Commit bd20a91f authored by jackfrued's avatar jackfrued

修正了文档上的部分bug

parent 6411875f
...@@ -228,4 +228,3 @@ print('个人所得税: ¥%.2f元' % tax) ...@@ -228,4 +228,3 @@ print('个人所得税: ¥%.2f元' % tax)
print('实际到手收入: ¥%.2f元' % (diff + 3500 - tax)) print('实际到手收入: ¥%.2f元' % (diff + 3500 - tax))
``` ```
>**说明:**上面的代码中使用了Python内置的`abs()`函数取绝对值来处理`-0`的问题。 >**说明:**上面的代码中使用了Python内置的`abs()`函数取绝对值来处理`-0`的问题。
...@@ -59,7 +59,6 @@ for x in range(1, 101): ...@@ -59,7 +59,6 @@ for x in range(1, 101):
if x % 2 == 0: if x % 2 == 0:
sum += x sum += x
print(sum) print(sum)
``` ```
### while循环 ### while循环
...@@ -125,7 +124,6 @@ Version: 0.1 ...@@ -125,7 +124,6 @@ Version: 0.1
Author: 骆昊 Author: 骆昊
Date: 2018-03-01 Date: 2018-03-01
""" """
from math import sqrt from math import sqrt
num = int(input('请输入一个正整数: ')) num = int(input('请输入一个正整数: '))
...@@ -155,13 +153,12 @@ Date: 2018-03-01 ...@@ -155,13 +153,12 @@ Date: 2018-03-01
x = int(input('x = ')) x = int(input('x = '))
y = int(input('y = ')) y = int(input('y = '))
if x > y: if x > y:
(x, y) = (y, x) x, y = y, x
for factor in range(x, 0, -1): for factor in range(x, 0, -1):
if x % factor == 0 and y % factor == 0: if x % factor == 0 and y % factor == 0:
print('%d和%d的最大公约数是%d' % (x, y, factor)) print('%d和%d的最大公约数是%d' % (x, y, factor))
print('%d和%d的最小公倍数是%d' % (x, y, x * y // factor)) print('%d和%d的最小公倍数是%d' % (x, y, x * y // factor))
break break
``` ```
#### 练习3:打印三角形图案。 #### 练习3:打印三角形图案。
...@@ -214,4 +211,3 @@ for i in range(row): ...@@ -214,4 +211,3 @@ for i in range(row):
print('*', end='') print('*', end='')
print() print()
``` ```
...@@ -11,7 +11,7 @@ class Rect(object): ...@@ -11,7 +11,7 @@ class Rect(object):
"""矩形类""" """矩形类"""
def __init__(self, width=0, height=0): def __init__(self, width=0, height=0):
"""构造器""" """初始化方法"""
self.__width = width self.__width = width
self.__height = height self.__height = height
......
...@@ -44,7 +44,7 @@ class Student(object): ...@@ -44,7 +44,7 @@ class Student(object):
if self.age < 18: if self.age < 18:
print('%s只能观看《熊出没》.' % self.name) print('%s只能观看《熊出没》.' % self.name)
else: else:
print('%s正在观看岛国爱情动作片.' % self.name) print('%s正在观看岛国爱情动作片.' % self.name
``` ```
> **说明**:写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。 > **说明**:写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。
...@@ -132,13 +132,10 @@ if __name__ == "__main__": ...@@ -132,13 +132,10 @@ if __name__ == "__main__":
```Python ```Python
class Clock(object): class Clock(object):
""" """数字时钟"""
数字时钟
"""
def __init__(self, hour=0, minute=0, second=0): def __init__(self, hour=0, minute=0, second=0):
""" """初始化方法
构造器
:param hour: 时 :param hour: 时
:param minute: 分 :param minute: 分
...@@ -187,8 +184,7 @@ from math import sqrt ...@@ -187,8 +184,7 @@ from math import sqrt
class Point(object): class Point(object):
def __init__(self, x=0, y=0): def __init__(self, x=0, y=0):
""" """初始化方法
构造器
:param x: 横坐标 :param x: 横坐标
:param y: 纵坐标 :param y: 纵坐标
...@@ -197,8 +193,7 @@ class Point(object): ...@@ -197,8 +193,7 @@ class Point(object):
self.y = y self.y = y
def move_to(self, x, y): def move_to(self, x, y):
""" """移动到指定位置
移动到指定位置
:param x: 新的横坐标 :param x: 新的横坐标
"param y: 新的纵坐标 "param y: 新的纵坐标
...@@ -207,8 +202,7 @@ class Point(object): ...@@ -207,8 +202,7 @@ class Point(object):
self.y = y self.y = y
def move_by(self, dx, dy): def move_by(self, dx, dy):
""" """移动指定的增量
移动指定的增量
:param dx: 横坐标的增量 :param dx: 横坐标的增量
"param dy: 纵坐标的增量 "param dy: 纵坐标的增量
...@@ -217,8 +211,7 @@ class Point(object): ...@@ -217,8 +211,7 @@ class Point(object):
self.y += dy self.y += dy
def distance_to(self, other): def distance_to(self, other):
""" """计算与另一个点的距离
计算与另一个点的距离
:param other: 另一个点 :param other: 另一个点
""" """
......
...@@ -345,8 +345,7 @@ class Fighter(object, metaclass=ABCMeta): ...@@ -345,8 +345,7 @@ class Fighter(object, metaclass=ABCMeta):
__slots__ = ('_name', '_hp') __slots__ = ('_name', '_hp')
def __init__(self, name, hp): def __init__(self, name, hp):
""" """初始化方法
初始化方法
:param name: 名字 :param name: 名字
:param hp: 生命值 :param hp: 生命值
...@@ -372,8 +371,7 @@ class Fighter(object, metaclass=ABCMeta): ...@@ -372,8 +371,7 @@ class Fighter(object, metaclass=ABCMeta):
@abstractmethod @abstractmethod
def attack(self, other): def attack(self, other):
""" """攻击
攻击
:param other: 被攻击的对象 :param other: 被攻击的对象
""" """
...@@ -386,8 +384,7 @@ class Ultraman(Fighter): ...@@ -386,8 +384,7 @@ class Ultraman(Fighter):
__slots__ = ('_name', '_hp', '_mp') __slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp): def __init__(self, name, hp, mp):
""" """初始化方法
初始化方法
:param name: 名字 :param name: 名字
:param hp: 生命值 :param hp: 生命值
...@@ -400,8 +397,7 @@ class Ultraman(Fighter): ...@@ -400,8 +397,7 @@ class Ultraman(Fighter):
other.hp -= randint(15, 25) other.hp -= randint(15, 25)
def huge_attack(self, other): def huge_attack(self, other):
""" """究极必杀技(打掉对方至少50点或四分之三的血)
究极必杀技(打掉对方至少50点或四分之三的血)
:param other: 被攻击的对象 :param other: 被攻击的对象
...@@ -418,8 +414,7 @@ class Ultraman(Fighter): ...@@ -418,8 +414,7 @@ class Ultraman(Fighter):
return False return False
def magic_attack(self, others): def magic_attack(self, others):
""" """魔法攻击
魔法攻击
:param others: 被攻击的群体 :param others: 被攻击的群体
......
...@@ -31,7 +31,6 @@ def main(): ...@@ -31,7 +31,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
请注意上面的代码,如果`open`函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理,如下所示。 请注意上面的代码,如果`open`函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理,如下所示。
...@@ -55,7 +54,6 @@ def main(): ...@@ -55,7 +54,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
在Python中,我们可以将那些在运行时可能会出现状况的代码放在`try`代码块中,在`try`代码块的后面可以跟上一个或多个`except`来捕获可能出现的异常状况。例如在上面读取文件的过程中,文件找不到会引发`FileNotFoundError`,指定了未知的编码会引发`LookupError`,而如果读取文件时无法按指定方式解码会引发`UnicodeDecodeError`,我们在`try`后面跟上了三个`except`分别处理这三种不同的异常状况。最后我们使用`finally`代码块来关闭打开的文件,释放掉程序中获取的外部资源,由于`finally`块的代码不论程序正常还是异常都会执行到(甚至是调用了`sys`模块的`exit`函数退出Python环境,`finally`块都会被执行,因为`exit`函数实质上是引发了`SystemExit`异常),因此我们通常把`finally`块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在`finally`代码块中关闭文件对象释放资源,也可以使用上下文语法,通过`with`关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。 在Python中,我们可以将那些在运行时可能会出现状况的代码放在`try`代码块中,在`try`代码块的后面可以跟上一个或多个`except`来捕获可能出现的异常状况。例如在上面读取文件的过程中,文件找不到会引发`FileNotFoundError`,指定了未知的编码会引发`LookupError`,而如果读取文件时无法按指定方式解码会引发`UnicodeDecodeError`,我们在`try`后面跟上了三个`except`分别处理这三种不同的异常状况。最后我们使用`finally`代码块来关闭打开的文件,释放掉程序中获取的外部资源,由于`finally`块的代码不论程序正常还是异常都会执行到(甚至是调用了`sys`模块的`exit`函数退出Python环境,`finally`块都会被执行,因为`exit`函数实质上是引发了`SystemExit`异常),因此我们通常把`finally`块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在`finally`代码块中关闭文件对象释放资源,也可以使用上下文语法,通过`with`关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。
...@@ -75,7 +73,6 @@ def main(): ...@@ -75,7 +73,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
除了使用文件对象的`read`方法读取文件之外,还可以使用`for-in`循环逐行读取或者用`readlines`方法将文件按行读取到一个列表容器中,代码如下所示。 除了使用文件对象的`read`方法读取文件之外,还可以使用`for-in`循环逐行读取或者用`readlines`方法将文件按行读取到一个列表容器中,代码如下所示。
...@@ -104,7 +101,6 @@ def main(): ...@@ -104,7 +101,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
要将文本信息写入文件文件也非常简单,在使用`open`函数时指定好文件名并将文件模式设置为`'w'`即可。注意如果需要对文件内容进行追加式写入,应该将模式设置为`'a'`。如果要写入的文件不存在会自动创建文件而不是引发异常。下面的例子演示了如何将1~9999直接的素数分别写入三个文件中(1~99之间的素数保存在a.txt中,100~999之间的素数保存在b.txt中,1000~9999之间的素数保存在c.txt中)。 要将文本信息写入文件文件也非常简单,在使用`open`函数时指定好文件名并将文件模式设置为`'w'`即可。注意如果需要对文件内容进行追加式写入,应该将模式设置为`'a'`。如果要写入的文件不存在会自动创建文件而不是引发异常。下面的例子演示了如何将1~9999直接的素数分别写入三个文件中(1~99之间的素数保存在a.txt中,100~999之间的素数保存在b.txt中,1000~9999之间的素数保存在c.txt中)。
...@@ -147,7 +143,6 @@ def main(): ...@@ -147,7 +143,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
### 读写二进制文件 ### 读写二进制文件
...@@ -171,7 +166,6 @@ def main(): ...@@ -171,7 +166,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
### 读写JSON文件 ### 读写JSON文件
...@@ -240,15 +234,14 @@ def main(): ...@@ -240,15 +234,14 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
json模块主要有四个比较重要的函数,分别是: json模块主要有四个比较重要的函数,分别是:
- dump - 将Python对象按照JSON格式序列化到文件中 - `dump` - 将Python对象按照JSON格式序列化到文件中
- dumps - 将Python对象处理成JSON格式的字符串 - `dumps` - 将Python对象处理成JSON格式的字符串
- load - 将文件中的JSON数据反序列化成对象 - `load` - 将文件中的JSON数据反序列化成对象
- loads - 将字符串的内容反序列化成Python对象 - `loads` - 将字符串的内容反序列化成Python对象
这里出现了两个概念,一个叫序列化,一个叫反序列化。自由的百科全书[维基百科](https://zh.wikipedia.org/)上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。 这里出现了两个概念,一个叫序列化,一个叫反序列化。自由的百科全书[维基百科](https://zh.wikipedia.org/)上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。
...@@ -268,7 +261,6 @@ def main(): ...@@ -268,7 +261,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
在Python中要实现序列化和反序列化除了使用json模块之外,还可以使用pickle和shelve模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别。关于这两个模块的相关知识可以自己看看网络上的资料。另外,如果要了解更多的关于Python异常机制的知识,可以看看segmentfault上面的文章[《总结:Python中的异常处理》](https://segmentfault.com/a/1190000007736783),这篇文章不仅介绍了Python中异常机制的使用,还总结了一系列的最佳实践,很值得一读。 在Python中要实现序列化和反序列化除了使用json模块之外,还可以使用pickle和shelve模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别。关于这两个模块的相关知识可以自己看看网络上的资料。另外,如果要了解更多的关于Python异常机制的知识,可以看看segmentfault上面的文章[《总结:Python中的异常处理》](https://segmentfault.com/a/1190000007736783),这篇文章不仅介绍了Python中异常机制的使用,还总结了一系列的最佳实践,很值得一读。
\ No newline at end of file
...@@ -72,15 +72,10 @@ Python提供了re模块来支持正则表达式相关操作,下面是re模块 ...@@ -72,15 +72,10 @@ Python提供了re模块来支持正则表达式相关操作,下面是re模块
```Python ```Python
""" """
验证输入用户名和QQ号是否有效并给出对应的提示信息 验证输入用户名和QQ号是否有效并给出对应的提示信息
要求: 要求:用户名必须由字母、数字或下划线构成且长度在6~20个字符之间,QQ号是5~12的数字且首位不能为0
用户名必须由字母、数字或下划线构成且长度在6~20个字符之间
QQ号是5~12的数字且首位不能为0
""" """
import re import re
...@@ -101,7 +96,6 @@ def main(): ...@@ -101,7 +96,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
> **提示**:上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了r),所谓“原始字符串”就是字符串中的每个字符都是它原始的意义,说得更直接一点就是字符串中没有所谓的转义字符啦。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作\\\\,例如表示数字的\\d得书写成\\\\d,这样不仅写起来不方便,阅读的时候也会很吃力。 > **提示**:上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了r),所谓“原始字符串”就是字符串中的每个字符都是它原始的意义,说得更直接一点就是字符串中没有所谓的转义字符啦。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作\\\\,例如表示数字的\\d得书写成\\\\d,这样不仅写起来不方便,阅读的时候也会很吃力。
...@@ -140,7 +134,6 @@ def main(): ...@@ -140,7 +134,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
> **说明**:上面匹配国内手机号的正则表达式并不够好,因为像14开头的号码只有145或147,而上面的正则表达式并没有考虑这种情况,要匹配国内手机号,更好的正则表达式的写法是:`(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)`,国内最近好像有19和16开头的手机号了,但是这个暂时不在我们考虑之列。 > **说明**:上面匹配国内手机号的正则表达式并不够好,因为像14开头的号码只有145或147,而上面的正则表达式并没有考虑这种情况,要匹配国内手机号,更好的正则表达式的写法是:`(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)`,国内最近好像有19和16开头的手机号了,但是这个暂时不在我们考虑之列。
...@@ -153,14 +146,13 @@ import re ...@@ -153,14 +146,13 @@ import re
def main(): def main():
sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.' sentence = '你丫是傻叉吗? 我操你大爷的. Fuck you.'
purified = re.sub('[操肏艹草曹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔', purified = re.sub('[操肏艹]|fuck|shit|傻[比屄逼叉缺吊屌]|煞笔',
'*', sentence, flags=re.IGNORECASE) '*', sentence, flags=re.IGNORECASE)
print(purified) # 你丫是*吗? 我*你大爷的. * you. print(purified) # 你丫是*吗? 我*你大爷的. * you.
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
> **说明**:re模块的正则表达式相关函数中都有一个flags参数,它代表了正则表达式的匹配标记,可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值,可以使用[按位或运算符](http://www.runoob.com/python/python-operators.html#ysf5)进行叠加,如`flags=re.I | re.M`。 > **说明**:re模块的正则表达式相关函数中都有一个flags参数,它代表了正则表达式的匹配标记,可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值,可以使用[按位或运算符](http://www.runoob.com/python/python-operators.html#ysf5)进行叠加,如`flags=re.I | re.M`。
...@@ -181,7 +173,6 @@ def main(): ...@@ -181,7 +173,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
### 后话 ### 后话
......
...@@ -123,7 +123,6 @@ def main(): ...@@ -123,7 +123,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
看起来没毛病,但是最后的结果是Ping和Pong各输出了10个,Why?当我们在程序中创建进程的时候,子进程复制了父进程及其所有的数据结构,每个子进程有自己独立的内存空间,这也就意味着两个子进程中各有一个`counter`变量,所以结果也就可想而知了。要解决这个问题比较简单的办法是使用multiprocessing模块中的`Queue`类,它是可以被多个进程共享的队列,底层是通过管道和[信号量(semaphore)]()机制来实现的,有兴趣的读者可以自己尝试一下。 看起来没毛病,但是最后的结果是Ping和Pong各输出了10个,Why?当我们在程序中创建进程的时候,子进程复制了父进程及其所有的数据结构,每个子进程有自己独立的内存空间,这也就意味着两个子进程中各有一个`counter`变量,所以结果也就可想而知了。要解决这个问题比较简单的办法是使用multiprocessing模块中的`Queue`类,它是可以被多个进程共享的队列,底层是通过管道和[信号量(semaphore)]()机制来实现的,有兴趣的读者可以自己尝试一下。
...@@ -160,7 +159,6 @@ def main(): ...@@ -160,7 +159,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
我们可以直接使用threading模块的`Thread`类来创建线程,但是我们之前讲过一个非常重要的概念叫“继承”,我们可以从已有的类创建新类,因此也可以通过继承`Thread`类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。代码如下所示。 我们可以直接使用threading模块的`Thread`类来创建线程,但是我们之前讲过一个非常重要的概念叫“继承”,我们可以从已有的类创建新类,因此也可以通过继承`Thread`类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。代码如下所示。
...@@ -198,7 +196,6 @@ def main(): ...@@ -198,7 +196,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
因为多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面的例子演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果。 因为多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面的例子演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果。
...@@ -253,7 +250,6 @@ def main(): ...@@ -253,7 +250,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
运行上面的程序,结果让人大跌眼镜,100个线程分别向账户中转入1元钱,结果居然远远小于100元。之所以出现这种情况是因为我们没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,会一起执行到`new_balance = self._balance + money`这行代码,多个线程得到的账户余额都是初始状态下的`0`,所以都是`0`上面做了+1的操作,因此得到了错误的结果。在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。下面的代码演示了如何使用“锁”来保护对银行账户的操作,从而获得正确的结果。 运行上面的程序,结果让人大跌眼镜,100个线程分别向账户中转入1元钱,结果居然远远小于100元。之所以出现这种情况是因为我们没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,会一起执行到`new_balance = self._balance + money`这行代码,多个线程得到的账户余额都是初始状态下的`0`,所以都是`0`上面做了+1的操作,因此得到了错误的结果。在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。下面的代码演示了如何使用“锁”来保护对银行账户的操作,从而获得正确的结果。
...@@ -310,7 +306,6 @@ def main(): ...@@ -310,7 +306,6 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
``` ```
比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题,但是即便如此,就如我们之前举的例子,使用多线程在提升执行效率和改善用户体验方面仍然是有积极意义的。 比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题,但是即便如此,就如我们之前举的例子,使用多线程在提升执行效率和改善用户体验方面仍然是有积极意义的。
...@@ -491,3 +486,4 @@ if __name__ == '__main__': ...@@ -491,3 +486,4 @@ if __name__ == '__main__':
``` ```
比较两段代码的执行结果(在我目前使用的MacBook上,上面的代码需要大概6秒左右的时间,而下面的代码只需要不到1秒的时间,再强调一次我们只是比较了运算的时间,不考虑列表创建及切片操作花费的时间),使用多进程后由于获得了更多的CPU执行时间以及更好的利用了CPU的多核特性,明显的减少了程序的执行时间,而且计算量越大效果越明显。当然,如果愿意还可以将多个进程部署在不同的计算机上,做成分布式进程,具体的做法就是通过multiprocessing.managers模块中提供的管理器将`Queue`对象通过网络共享出来(注册到网络上让其他计算机可以访问),这部分内容也留到爬虫的专题再进行讲解。 比较两段代码的执行结果(在我目前使用的MacBook上,上面的代码需要大概6秒左右的时间,而下面的代码只需要不到1秒的时间,再强调一次我们只是比较了运算的时间,不考虑列表创建及切片操作花费的时间),使用多进程后由于获得了更多的CPU执行时间以及更好的利用了CPU的多核特性,明显的减少了程序的执行时间,而且计算量越大效果越明显。当然,如果愿意还可以将多个进程部署在不同的计算机上,做成分布式进程,具体的做法就是通过multiprocessing.managers模块中提供的管理器将`Queue`对象通过网络共享出来(注册到网络上让其他计算机可以访问),这部分内容也留到爬虫的专题再进行讲解。
...@@ -296,3 +296,4 @@ if __name__ == '__main__': ...@@ -296,3 +296,4 @@ if __name__ == '__main__':
#### UDP套接字 #### UDP套接字
传输层除了有可靠的传输协议TCP之外,还有一种非常轻便的传输协议叫做用户数据报协议,简称UDP。TCP和UDP都是提供端到端传输服务的协议,二者的差别就如同打电话和发短信的区别,后者不对传输的可靠性和可达性做出任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和而不是数据完整性的场景中(例如传输网络音视频数据),UDP可能是更好的选择。可能大家会注意到一个现象,就是在观看网络视频时,有时会出现卡顿,有时会出现花屏,这无非就是部分数据传丢或传错造成的。在Python中也可以使用UDP套接字来创建网络应用,对此我们不进行赘述,有兴趣的读者可以自行研究。 传输层除了有可靠的传输协议TCP之外,还有一种非常轻便的传输协议叫做用户数据报协议,简称UDP。TCP和UDP都是提供端到端传输服务的协议,二者的差别就如同打电话和发短信的区别,后者不对传输的可靠性和可达性做出任何承诺从而避免了TCP中握手和重传的开销,所以在强调性能和而不是数据完整性的场景中(例如传输网络音视频数据),UDP可能是更好的选择。可能大家会注意到一个现象,就是在观看网络视频时,有时会出现卡顿,有时会出现花屏,这无非就是部分数据传丢或传错造成的。在Python中也可以使用UDP套接字来创建网络应用,对此我们不进行赘述,有兴趣的读者可以自行研究。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment