Python程序笔记20230302
Posted taurusxw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python程序笔记20230302相关的知识,希望对你有一定的参考价值。
Alice、Bob 和他们的朋友们
问题主体
密码学家 Rivest、Shamir、Adleman 于1977年4月撰写了一篇论文《数字签名与公钥密码学》(On Digital Signatures and Public-Key Cryptosystems),并投稿至了一个期刊上,不过很遗憾这篇论文被拒稿了。随后他们修改了论文,并将论文重新命名为《一种实现数字签名和公钥密码系统的方法》(A Method of Obtaining Digital Signatures and Public-Key Cryptosystems),最终于1978年2月成功发表于顶级期刊《ACM 通信》(Communications of the ACM)。在这篇论文中,三位密码学家嫌弃使用 A、B 两个字母代表角色太无聊,就用 Alice 和 Bob 来代替 A 和 B。
在随后的几十年里密码学界又新增了很多著名人物。布鲁斯·施奈尔所著的《应用密码学》(Applied Cryptography)里详细列举了这些人物,下面是一些例子:
crypto_roles = [
\'爱丽丝(Alice)是信息发送者。\',
\'与鲍伯(Bob)是信息接受者。通例上,爱丽丝希望把一条消息发送给鲍伯。\',
\'卡罗尔或查利(Carol或Charlie)是通信中的第三位参加者。\',
\'戴夫(Dave)是通信中的第四位参加者。\',
\'伊夫(Eve)是一位偷听者(eavesdropper),但行为通常是被动的。她拥有偷听的技术,但不会中途篡改发送的消息。在量子密码学中,伊夫也可以指环境(environment)。\'
]
Python 是一门多范式编程语言,其中包括面向对象编程。
首先,我们用 Python 类(class)
定义一个密码城邦人物类型:
class CryptographyPeople:
def __init__(self, name_cn, name_en, role, desc):
self.name_cn = name_cn
self.name_en = name_en
self.role = role
self.desc = desc
其次,我们添加一个简易的密码城邦人物解析器,它的作用是将类似\'马提尔达(Matilda)是一位商人(merchant),用于电子商务。\',
这样的人物剧本
解析成CryptographyPeople
,创建一个密码城邦人物:
class SimpleCryptographyPeopleParser:
def __init__(self, text) -> None:
self.text = text
def parse(self, desc):
# 解析名字部分
name_cn, name_en, rest = self.parse_name(desc)
# 解析角色部分
role, rest = self.parse_role(rest)
# 解析描述不符
desc = self.parse_desc(rest)
# 创建密码城邦人物
people = CryptographyPeople(name_cn, name_en, role, desc)
return people
def parse_name(self, text):
# 解析名字部分
index = text.find(\'是\')
name, rest = text[0:index], text[index+1:]
# 解析中英文名字
start = name.find(\'(\')
end = name.find(\')\')
name_cn = name[0:start]
name_en = name[start+1:end]
return name_cn.strip(), name_en.strip(), rest
def parse_role(self, text):
index1 = text.find(\'。\')
index2 = text.find(\',\')
index = 0
if index1 > 0 and index2 > 0:
index = min(index1, index2)
else:
index = max(index1, index2)
role, rest = text[0:index], text[index+1:len(text)-1]
# 去除冗余量词
counts = [\'一名\', \'一位\', \'一个\']
for count in counts:
role = role.replace(count, \'\')
return role.strip(), rest.strip()
def parse_desc(self, name_cn, name_en, role, rest):
desc = rest
if desc:
# 识别自我主语
self_list = [name_cn, \'他\', \'她\']
for self_item in self_list:
desc = desc.replace(self_item, \'我\')
else:
# 补充默认描述
desc = \'很高兴认识你\'
最后,我们希望创建一个密码城邦,它包含 add
和 introduce
两个方法:
class CryptographyCity:
def __init__(self):
self.peoples = []
def add(self, text):
parser = SimpleCryptographyPeopleParser(text)
people = parser.parse(text)
self.peoples.append(people)
# TODO(YOU): 请在此实现 introduce 方法
最终,我们可以构建起密码城邦,并让市民们全部自报家门:
if __name__ == \'__main__\':
crypto_roles = ...
city = CryptographyCity()
for crypto_role in crypto_roles:
city.add(crypto_role)
city.introduce()
密码城邦人物的自我介绍如下:
爱丽丝(Alice): 密码学家说我是一位信息发送者,很高兴认识你。
鲍伯(Bob): 密码学家说我是一位信息接受者,通例上,爱丽丝希望把一条消息发送给我。
...
结构分析 1
def __init__(self, name_cn, name_en, role, desc):
def __init__
是 Python 中的一个特殊方法,也称为类的构造函数。它是在创建一个新的类实例时被自动调用的方法,用于初始化实例的属性。
init 的作用
__init__
的作用有两种,一种是用于定义类的构造函数,另一种是用于标识一个目录是一个 python 的模块包。
第一种作用,就是在创建类的对象时初始化对象的属性或执行一些其他操作。
第二种作用,是当一个目录中包含了 __init__.py
文件时,这个目录就会被认为是一个 python 的模块包,可以被 import 导入。
魔法函数
在 python 中,有一些方法或变量的名字是以双下划线“__
”开头和结尾的,它们是 python 的魔法函数或魔法变量,有一些特殊的含义或功能。前后都加上“__
”,是为了区别它和普通的方法或变量,表示它是一个魔法函数,不是用户自定义的函数。这样可以避免命名冲突,也可以让 python 解释器识别它并执行相应的操作。
双下划线开头和结尾的魔法函数是 Python 的内置函数,一般以__xx__的形式命名,每个魔法函数对应一个内置函数或者运算符,比如 __init__
对应构造函数,__str__
对应 str ()函数,__add__
对应+运算符等。
双下划线开头和结尾的魔法函数是 Python 的一种高级语法,允许你在类中自定义函数,并绑定到类的特殊方法中,从而为类提供一些额外的功能或特性,比如运算符重载、迭代器、上下文管理器等。
双下划线开头和结尾的魔法函数是 Python 社区一致认可的命名约定,用来区分普通的方法和特殊的方法,避免与用户自定义的方法名冲突。
常用的魔法函数有以下几种:
__str__(self)
:返回对象的字符串表示,通常用于将对象转换为人类可读的字符串格式。__repr__(self)
:返回对象的字符串表示,通常用于将对象转换为程序可读的字符串格式。__len__(self)
:返回对象的长度,通常用于计算容器对象中元素的个数。__getitem__(self, key)
:根据给定的键获取对象中的值,通常用于实现索引操作,支持通过索引或切片访问容器对象中的元素。__setitem__(self, key, value)
:根据给定的键设置对象中的值,通常用于实现赋值操作,支持通过索引或切片修改容器对象中的元素。__call__(self, *args, **kwargs)
:将对象作为函数调用,通常用于实现可调用对象,使得对象可以像函数一样被调用。__eq__(self, other)
:比较两个对象是否相等,通常用于实现对象的比较操作。__lt__(self, other)
:比较两个对象的大小关系,通常用于实现对象的排序操作。__add__(self, other)
:将两个对象相加,通常用于实现对象的加法操作。__sub__(self, other)
:将两个对象相减,通常用于实现对象的减法操作。
如果您想判断一个对象是否实现了某个魔法函数,您可以使用以下的方法:
- 使用 hasattr ()函数,传入对象和魔法函数的名称(字符串形式),如果返回 True,表示对象实现了该魔法函数,否则表示没有实现。例如,
hasattr(obj, “__str__”)
可以判断 obj 是否实现了__str__方法。 - 使用 callable ()函数,传入对象和魔法函数的名称(属性形式),如果返回 True,表示对象实现了该魔法函数,否则表示没有实现。例如,
callable(obj.__str__)
可以判断 obj 是否实现了__str__方法。 - 使用 try-except 语句,尝试调用对象和魔法函数的名称(属性形式),如果没有抛出异常,表示对象实现了该魔法函数,否则表示没有实现。例如,
try: obj.__str__() except: print(“obj没有实现__str__方法”)
可以判断 obj 是否实现了__str__方法。
下面是一个简单的例子,判断一个列表对象是否实现了以下几个魔法函数:
lst = [1, 2, 3]
# 使用hasattr()函数
print(hasattr(lst, "__len__")) # True
print(hasattr(lst, "__add__")) # True
print(hasattr(lst, "__call__")) # False
# 使用callable()函数
print(callable(lst.__len__)) # True
print(callable(lst.__add__)) # True
print(callable(lst.__call__)) # False
# 使用try-except语句
try:
lst.__len__()
except:
print("lst没有实现__len__方法")
else:
print("lst实现了__len__方法")
try:
lst.__add__(lst)
except:
print("lst没有实现__add__方法")
else:
print("lst实现了__add__方法")
try:
lst.__call__()
except:
print("lst没有实现__call__方法")
else:
print("lst实现了__call__方法")
结构分析 2
def parse_role(self, text):
index1 = text.find(\'。\')
index2 = text.find(\',\')
index = 0
if index1 > 0 and index2 > 0:
index = min(index1, index2)
else:
index = max(index1, index2)
role, rest = text[0:index], text[index+1:len(text)-1]
# 去除冗余量词
counts = [\'一名\', \'一位\', \'一个\']
for count in counts:
role = role.replace(count, \'\')
return role.strip(), rest.strip()
当解析角色部分时,parse_role
方法接收一个字符串参数 text
,该字符串代表密码城邦人物的角色信息。该方法首先使用 find
方法查找字符串中第一个出现句号 。
和逗号 ,
的位置,并根据它们的位置来截取字符串。这个位置的索引就是字符串 index
的值。如果在字符串中没有找到句号或逗号,index
将为0,表示没有角色信息。
然后,该方法从text
字符串中截取角色字符串role
和剩余信息字符串rest
。在剩余信息中,从字符串开头和结尾中删除role
字符串的字符,以获得纯粹的剩余信息。
最后,该方法通过遍历预定义的冗余量词列表 counts
,使用 replace
方法删除角色字符串中的冗余量词。最后返回清理后的角色字符串 role
和剩余信息字符串 rest
。
def parse_desc(self, name_cn, name_en, role, rest):
desc = rest
if desc:
# 识别自我主语
self_list = [name_cn, \'他\', \'她\']
for self_item in self_list:
desc = desc.replace(self_item, \'我\')
else:
# 补充默认描述
desc = \'很高兴认识你\'
这段代码是 SimpleCryptographyPeopleParser
类中的 parse_desc
方法,用于解析人物的描述部分。
方法接受四个参数,分别是人物的中文名字、英文名字、角色和剩余的未解析部分。方法首先将未解析部分作为描述赋值给变量 desc
,然后判断 desc
是否为空。如果不为空,即有描述部分,就会进行自我主语的识别和替换。
这里定义了一个 self_list
列表,包含了人物的中文名字、"他" 和 "她" 三种可能的自我主语。接下来使用 replace
函数将其中的每个元素都替换成 "我",以使描述符合第一人称视角的要求。如果 desc
为空,则将描述赋值为 "很高兴认识你",作为默认描述。最后返回处理后的描述。
补全代码
前半部分
class CryptographyCity:
def __init__(self):
self.peoples = []
def add(self, text):
parser = SimpleCryptographyPeopleParser(text)
people = parser.parse(text)
self.peoples.append(people)
这段代码定义了一个名为 CryptographyCity 的类,这个类表示一个密码城邦,拥有一个属性 peoples
,代表该城邦的人物角色列表。
这个类还定义了一个方法add
,用于向城邦中添加新的人物角色。该方法接受一个字符串参数text
,并使用SimpleCryptographyPeopleParser
类的实例parser
来解析这个字符串。parser
实例将字符串解析为一个CryptographyPeople
对象,表示一个密码城邦的人物角色,然后将这个对象添加到城邦的peoples
列表中。
中间部分
def introduce(self):
for people in self.peoples:
self.say(people)
def introduce(self):
for people in peoples:
say(people)
def introduce(self):
i = 0
while i < len(self.peoples):
people = self.peoples[i]
self.say(people)
i += 1
def introduce(self):
[self.say(people) for people in self.peoples]
后面部分
def say(self, people):
info = f\'people.name_cn(people.name_en): 密码学家说我是一位people.role,people.desc。\'
print(info)
整体代码优化
# @Time:2022/12/14 0014 16:12
# @Author:晚秋拾叶
# @File:AliceBob.py
# @PyCharm之Python
# 定义一个密码城邦人物类型
class CryptographyPeople:
def __init__(self, name_cn, name_en, role, desc):
self.name_cn = name_cn
self.name_en = name_en
self.role = role
self.desc = desc
# 简易密码城邦人物解析器,它的作用是将类似\'马提尔达(Matilda)是一位商人(merchant),用于电子商务。\',
# 这样的人物剧本解析成CryptographyPeople,创建一个密码城邦人物
# 单个人物的解析
class SimpleCryptographyPeopleParser:
def __init__(self, text) -> None:
self.text = text
# 1.名字解析,包括中文和英文两部分,并返回
def parse_name(self, text):
# 解析名字部分,析出name和rest
index = text.find(\'是\')
name = text[0:index]
# 解析中英文名字
start = name.find(\'(\')
end = name.find(\')\')
name_cn = name[0:start].strip()
name_en = name[start + 1:end].strip()
# 返回中文、英文名字和剩余文字
return name_cn, name_en
# 2. 解析密码城邦人物的角色部分
def parse_role(self, text):
index_is = text.find(\'是\')
index1 = text.find(\'。\')
index2 = text.find(\',\')
if index1 and index2:
index = min(index1, index2)
else:
index = max(index1, index2)
role = text[index_is:index]
# 去除冗余量词
counts = [\'一名\', \'一位\', \'一个\']
for count in counts:
role = role.replace(count, \'\').strip()
return role
# 3.对剩余部分的解析,进行优化处理,本人名字换为第一人称,如果没有剩余部分,再加上问候语。
def parse_desc(self, text):
index1 = text.find(\'。\')
index2 = text.find(\',\')
if index1 and index2:
index = min(index1, index2)
else:
index = max(index1, index2)
rest = text[index + 1:]
name_cn = self.parse_name(text)[0]
if index > 0:
# 识别自我主语
self_list = [name_cn, \'她\']
for self_item in self_list:
rest = rest.replace(self_item, \'我\')
else:
# 补充默认描述
rest = \'很高兴认识你。\'
return rest
# 总的解析,完成并返回一个清晰的人物
def parse(self, text):
# 把解析出来的中英文名字部分和剩余部分,分别放到两个对应的变量
name_cn, name_en = self.parse_name(text)
# 解析剩余部分,得到角色部分
role = self.parse_role(text)
# 解析不够长的描述部分
desc1 = self.parse_desc(self.text)
# 创建密码城邦人物介绍,内容包括中文名字,英文名字,人物角色,人物描述
people = CryptographyPeople(name_cn, name_en, role, desc1)
return people
# 说出密码城堡里的人物信息
class CryptographyCity:
def __init__(self):
self.peoples = []
# 把短文中的每个人物简介,解析成“中文名+英文名+描述”,然后放入列表peoples中
def add(self, text):
parser = SimpleCryptographyPeopleParser(text)
people = parser.parse(text)
self.peoples.append(people)
# 再调用say()方法,把peoples中的内容,更新组织起来,输出。
def introduce(self):
i = 0
while i < len(self.peoples):
people = self.peoples[i]
self.say(people)
i += 1
def say(self, people):
info = f\'people.name_cn(people.name_en): 密码学家说我是一位people.role,people.desc\'
print(info)
if __name__ == \'__main__\':
crypto_roles = [
\'爱丽丝(Alice)是信息发送者。\',
\'鲍伯(Bob)是信息接受者。通例上,爱丽丝希望把一条消息发送给鲍伯。\',
\'卡罗尔或查利(Carol或Charlie)是通信中的第三位参加者。\',
\'戴夫(Dave)是通信中的第四位参加者。\',
\'伊夫(Eve)是一位偷听者(eavesdropper),但行为通常是被动的。她拥有偷听的技术,但不会中途篡改发送的消息。\'
\'在量子密码学中,伊夫也可以指环境(environment)。\'
]
city = CryptographyCity()
for crypto_role in crypto_roles:
city.add(crypto_role)
city.introduce()
输出结果
爱丽丝 (Alice): 密码学家说我是一位是信息发送者,很高兴认识你。
鲍伯 (Bob): 密码学家说我是一位是信息接受者,通例上,爱丽丝希望把一条消息发送给我。
卡罗尔或查利 (Carol 或 Charlie): 密码学家说我是一位是通信中的第三位参加者,很高兴认识你。
戴夫 (Dave): 密码学家说我是一位是通信中的第四位参加者,很高兴认识你。
伊夫 (Eve): 密码学家说我是一位是偷听者(eavesdropper),但行为通常是被动的。我拥有偷听的技术,但不会中途篡改发送的消息。在量子密码学中,我也可以指环境(environment)。
编写一个二维向量类 Vector
一个简单的例子,实现了一个二维向量类 Vector,它支持加减、乘除、长度、字符串表示和迭代
class Vector(object):
# 构造函数
def __init__(self, x, y):
self.x = x
self.y = y
# 字符串表示
def __str__(self):
return "(,)".format(self.x, self.y)
# 加法运算
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# 减法运算
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
# 长度
def __len__(self):
return 2
# 迭代器
def __iter__(self):
self.index = 0
return self
# 迭代元素
def __next__(self):
if self.index < len(self):
value = (self.x, self.y)[self.index]
self.index += 1
return value
else:
raise StopIteration
# 乘法运算
def __mul__(self, other):
# 如果另一个对象也是向量,则进行点乘
if isinstance(other, Vector):
return self.x * other.x + self.y * other.y
# 如果另一个对象是数字,则进行标量乘法
elif isinstance(other, (int, float)):
return Vector(self.x * other, self.y * other)
# 否则抛出异常
else:
raise TypeError("Unsupported operand type(s) for *: \'Vector\' and \'\'".format(type(other)))
# 测试代码
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)
print(v1 - v2)
print(v1 * v2) # 11
print(v1 * 2) # (2,4)
print(v1 * "a") # TypeError: Unsupported operand type(s) for *: \'Vector\' and \'<class \'str\'>\'
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"(self.x, self.y)"
def __repr__(self):
return f"Vector(self.x, self.y)"
def __len__(self):
return 2
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, other):
if isinstance(other, (int, float)):
return Vector(self.x * other, self.y * other)
elif isinstance(other, Vector):
return Vector(self.x * other.x, self.y * other.y)
else:
raise TypeError(f"unsupported operand type(s) for *: \'type(self).__name__\' and \'type(other).__name__\'")
def __rmul__(self, other):
return self.__mul__(other)
def __truediv__(self, other):
if isinstance(other, (int, float)):
return Vector(self.x / other, self.y / other)
elif isinstance(other, Vector):
return Vector(self.x / other.x, self.y / other.y)
else:
raise TypeError(f"unsupported operand type(s) for /: \'type(self).__name__\' and \'type(other).__name__\'")
def __iter__(self):
self.current = 0
return self
def __next__(self):
if self.current >= 2:
raise StopIteration
if self.current == 0:
result = self.x
else:
result = self.y
self.current += 1
return result
def length(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = Vector(5, 6)
# 测试加法运算
print(v1 + v2) # 输出 (4, 6)
print(v1 + v2 + v3) # 输出 (9, 12)
# 测试减法运算
print(v1 - v2) # 输出 (-2, -2)
print(v1 - v2 - v3) # 输出 (-7, -8)
# 测试长度函数
print(abs(v1)) # 输出 2.23606797749979
print(abs(v2)) # 输出 5.0
# 测试字符串表示
print(str(v1)) # 输出 "<1, 2>"
print(str(v2)) # 输出 "<3, 4>"
# 测试迭代器
for i in v1:
print(i) # 输出 1 和 2
# 测试乘法运算
print(v1 * 2) # 输出 <2, 4>
print(2 * v1) # 输出 <2, 4>
# 测试除法运算
print(v1 / 2) # 输出 <0.5, 1.0>
编写一个类练习 self
如果想自己写一个类来练习一下 self,可以参考以下的步骤:
- 首先,需要确定您想要写的类的名称、属性和方法,以及它们的作用和功能。
- 然后,需要在类的定义中使用class关键字,以及括号中的object或其他基类(如果有的话)。
- 接着,需要在类中定义一个__init__方法,用来初始化类的实例属性。在
__init__
方法中,需要使用self参数来表示类的实例,并用self.属性名 = 参数名
的形式来赋值。 - 然后,需要在类中定义其他的实例方法,用来实现类的行为。在实例方法中,也需要使用self参数来表示类的实例,并用
self.属性名
或self.方法名
的形式来访问或调用。 - 最后,您需要创建类的实例对象,并调用它们的属性和方法,看看是否符合您的预期。
下面是一个简单的例子,写一个表示学生的类 Student,它有姓名、年龄和成绩三个属性,以及一个打印信息的方法:
# 定义Student类
class Student(object):
# 定义__init__方法
def __init__(self, name, age, score):
# 使用self参数来初始化实例属性
self.name = name
self.age = age
self.score = score
# 定义print_info方法
def print_info(self):
# 使用self参数来访问实例属性
print(f"姓名:self.name,年龄:self.age,成绩:self.score")
# 创建Student类的实例对象
s1 = Student("张三", 18, 90)
s2 = Student("李四", 19, 80)
# 调用实例对象的属性和方法
s1.print_info()
s2.print_info()
class Student:
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
self.classes = []
def __str__(self):
return f"self.name (self.age years old, self.grade grade)"
def __repr__(self):
return f"Student(\'self.name\', self.age, self.grade)"
def __len__(self):
return len(self.classes)
def __getitem__(self, index):
return self.classes[index]
def __setitem__(self, index, value):
self.classes[index] = value
def __add__(self, other):
name = f"self.name and other.name"
age = max(self.age, other.age)
grade = min(self.grade, other.grade)
new_student = Student(name, age, grade)
new_student.classes = self.classes + other.classes
return new_student
def add_class(self, class_name):
self.classes.append(class_name)
这个示例定义了一个 Student
类,每个学生都有一个名字、年龄和年级,还可以选择加入多个课程。这个类包括了 __init__
、__str__
、__repr__
、__len__
、__getitem__
、__setitem__
、__add__
等常用的魔法函数,它们分别用于初始化对象、返回对象的字符串表示、返回对象的程序可读表示、返回对象的长度、支持索引和切片访问容器对象中的元素、支持通过索引或切片修改容器对象中的元素、将两个对象相加等操作。
编写一个类练习静态方法和类方法
写一个类来练习一下静态方法和类方法,您可以参考以下的步骤:
- 首先,需要确定想要写的类的名称、属性和方法,以及它们的作用和功能。
- 然后,需要在类的定义中使用class关键字,以及括号中的object或其他基类(如果有的话)。
- 接着,需要在类中定义一个__init__方法,用来初始化类的实例属性。在__init__方法中,需要使用self参数来表示类的实例,并用
self.属性名 = 参数名
的形式来赋值。 - 然后,需要在类中定义一个或多个静态方法,用来实现一些与实例或类无关的功能。在静态方法中,不需要使用self或cls参数,只需使用@staticmethod装饰器来修饰。
- 然后,需要在类中定义一个或多个类方法,用来实现一些与类相关的功能。在类方法中,需要使用cls参数来表示类本身,并用@classmethod装饰器来修饰。
- 最后,需要创建类的实例对象,并调用它们的属性和方法,看看是否符合您的预期。
下面是一个简单的例子,写一个表示矩形的类 Rectangle,它有长和宽两个属性,以及一个计算面积的实例方法、一个计算周长的静态方法和一个根据面积创建矩形对象的类方法:
# 定义Rectangle类
class Rectangle(object):
# 定义__init__方法
def __init__(self, length, width):
# 使用self参数来初始化实例属性
self.length = length
self.width = width
# 定义area实例方法
def area(self):
# 使用self参数来访问实例属性
return self.length * self.width
# 定义perimeter静态方法
@staticmethod
def perimeter(length, width):
# 不需要使用self或cls参数
return (length + width) * 2
# 定义create类方法
@classmethod
def create(cls, area):
# 使用cls参数来表示类本身
length = area ** 0.5
width = length
return cls(length, width)
# 创建Rectangle类的实例对象
r1 = Rectangle(3, 4)
r2 = Rectangle.create(16)
# 调用实例对象的属性和方法
print(r1.length, r1.width)
print(r1.area())
print(r2.length, r2.width)
print(r2.area())
# 调用静态方法和类方法
print(Rectangle.perimeter(3, 4))
print(Rectangle.create(25).area())
python学习笔记-进程线程
1.什么是进程(process)?
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
2.什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
3.有了进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:
-
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
-
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。
再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!
4.Python GIL(Global Interpreter Lock)
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?
由于所有线程在cpu上都是分时间片执行的.举个例子来说
首先需要明确的一点是GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf
5.Python threading模块
线程有2种调用方式,如下:
直接调用:
1 import threading 2 import time 3 4 def sayhi(num): #定义每个线程要运行的函数 5 6 print("running on number:%s" %num) 7 8 time.sleep(3) 9 10 if __name__ == ‘__main__‘: 11 12 t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 13 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 14 15 t1.start() #启动线程 16 t2.start() #启动另一个线程 17 18 print(t1.getName()) #获取线程名 19 print(t2.getName())
继承式调用
1 import threading 2 import time 3 4 5 class MyThread(threading.Thread): 6 def __init__(self,num): 7 threading.Thread.__init__(self) 8 self.num = num 9 10 def run(self):#定义每个线程要运行的函数 11 12 print("running on number:%s" %self.num) 13 14 time.sleep(3) 15 16 if __name__ == ‘__main__‘: 17 18 t1 = MyThread(1) 19 t2 = MyThread(2) 20 t1.start() 21 t2.start()
6.Join & Daemon
一些线程执行后台任务,如发送keepalive数据包,或执行周期性垃圾收集,或其他。 这些仅在主程序运行时有用,一旦其他非守护进程线程退出,就可以关闭它们。
没有守护线程,你必须跟踪它们,并告诉他们退出,然后你的程序才能完全退出。 通过将它们设置为守护线程,您可以让它们运行并忘记它们,当您的程序退出时,任何守护线程都会自动终止。
示例:
1 import time 2 import threading 3 4 5 def run(n): 6 7 print(‘[%s]------running----\\n‘ % n) 8 time.sleep(2) 9 print(‘--done--‘) 10 11 def main(): 12 for i in range(5): 13 t = threading.Thread(target=run,args=[i,]) 14 t.start() 15 t.join(1) 16 print(‘starting thread‘, t.getName()) 17 18 19 m = threading.Thread(target=main,args=[]) 20 m.setDaemon(True) #将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 21 m.start() 22 m.join(timeout=2) 23 print("---main thread done----")
注意:守护线程在关机时突然停止。 他们的资源(如打开的文件,数据库事务等)可能无法正确释放。 如果你想让你的线程正常停止,使他们非守护进程,并使用一个合适的信号机制,如一个事件。
7.线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
未加锁版本 in python2
1 import time 2 import threading 3 4 def addNum(): 5 global num #在每个线程中都获取这个全局变量 6 print(‘--get num:‘,num ) 7 time.sleep(1) 8 num -=1 #对此公共变量进行-1操作 9 10 num = 100 #设定一个共享变量 11 thread_list = [] 12 for i in range(100): 13 t = threading.Thread(target=addNum) 14 t.start() 15 thread_list.append(t) 16 17 for t in thread_list: #等待所有线程执行完毕 18 t.join() 19 20 print(‘final num:‘, num )
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
*注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁版本
1 import time 2 import threading 3 4 def addNum(): 5 global num #在每个线程中都获取这个全局变量 6 print(‘--get num:‘,num ) 7 time.sleep(1) 8 lock.acquire() #修改数据前加锁 9 num -=1 #对此公共变量进行-1操作 10 lock.release() #修改后释放 11 12 num = 100 #设定一个共享变量 13 thread_list = [] 14 lock = threading.Lock() #生成全局锁 15 for i in range(100): 16 t = threading.Thread(target=addNum) 17 t.start() 18 thread_list.append(t) 19 20 for t in thread_list: #等待所有线程执行完毕 21 t.join() 22 23 print(‘final num:‘, num )
GIL VS Lock
既然Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下
既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
8.RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
1 import threading,time 2 3 def run1(): 4 print("grab the first part data") 5 lock.acquire() 6 global num 7 num +=1 8 lock.release() 9 return num 10 def run2(): 11 print("grab the second part data") 12 lock.acquire() 13 global num2 14 num2+=1 15 lock.release() 16 return num2 17 def run3(): 18 lock.acquire() 19 res = run1() 20 print(‘--------between run1 and run2-----‘) 21 res2 = run2() 22 lock.release() 23 print(res,res2) 24 25 26 if __name__ == ‘__main__‘: 27 28 num,num2 = 0,0 29 lock = threading.RLock() 30 for i in range(10): 31 t = threading.Thread(target=run3) 32 t.start() 33 34 while threading.active_count() != 1: 35 print(threading.active_count()) 36 else: 37 print(‘----all threads done---‘) 38 print(num,num2)
9.Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
1 import threading,time 2 3 def run(n): 4 semaphore.acquire() 5 time.sleep(1) 6 print("run the thread: %s\\n" %n) 7 semaphore.release() 8 9 if __name__ == ‘__main__‘: 10 11 num= 0 12 semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行 13 for i in range(20): 14 t = threading.Thread(target=run,args=(i,)) 15 t.start() 16 17 while threading.active_count() != 1: 18 pass #print threading.active_count() 19 else: 20 print(‘----all threads done---‘) 21 print(num)
10.Timer
此类表示应在一定时间过去后才应运行的操作
与线程一样,通过调用它们的start()方法来启动计时器。 可以通过调用thecancel()方法停止计时器(在动作开始之前)。 定时器在执行其操作之前将等待的间隔可能与用户指定的间隔不完全相同。
1 def hello(): 2 print("hello, world") 3 4 t = Timer(30.0, hello) 5 t.start() # after 30 seconds, "hello, world" will be printed
11.Events
事件是一个简单的同步对象;
该事件表示内部标志和线程
可以等待标志被设置,或者设置或清除标志本身。
event = threading.Event()
#一个客户线程可以等待设置的标志
event.wait()
#一个服务器线程可以设置或重置它
event.set()
event.clear()
如果设置了标志,wait方法不会做任何事情。
如果该标志被清除,等待将阻塞,直到它再次被设置。
任何数量的线程都可以等待相同的事件。
通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。
1 import threading,time 2 import random 3 def light(): 4 if not event.isSet(): 5 event.set() #wait就不阻塞 #绿灯状态 6 count = 0 7 while True: 8 if count < 10: 9 print(‘\\033[42;1m--green light on---\\033[0m‘) 10 elif count <13: 11 print(‘\\033[43;1m--yellow light on---\\033[0m‘) 12 elif count <20: 13 if event.isSet(): 14 event.clear() 15 print(‘\\033[41;1m--red light on---\\033[0m‘) 16 else: 17 count = 0 18 event.set() #打开绿灯 19 time.sleep(1) 20 count +=1 21 def car(n): 22 while 1: 23 time.sleep(random.randrange(10)) 24 if event.isSet(): #绿灯 25 print("car [%s] is running.." % n) 26 else: 27 print("car [%s] is waiting for the red light.." %n) 28 if __name__ == ‘__main__‘: 29 event = threading.Event() 30 Light = threading.Thread(target=light) 31 Light.start() 32 for i in range(3): 33 t = threading.Thread(target=car,args=(i,)) 34 t.start()
这里还有一个event使用的例子,员工进公司门要刷卡, 我们这里设置一个线程是“门”, 再设置几个线程为“员工”,员工看到门没打开,就刷卡,刷完卡,门开了,员工就可以通过。
1 import threading 2 import time 3 import random 4 5 def door(): 6 door_open_time_counter = 0 7 while True: 8 if door_swiping_event.is_set(): 9 print("\\033[32;1mdoor opening....\\033[0m") 10 door_open_time_counter +=1 11 12 else: 13 print("\\033[31;1mdoor closed...., swipe to open.\\033[0m") 14 door_open_time_counter = 0 #清空计时器 15 door_swiping_event.wait() 16 17 18 if door_open_time_counter > 3:#门开了已经3s了,该关了 19 door_swiping_event.clear() 20 21 time.sleep(0.5) 22 23 24 def staff(n): 25 26 print("staff [%s] is comming..." % n ) 27 while True: 28 if door_swiping_event.is_set(): 29 print("\\033[34;1mdoor is opened, passing.....\\033[0m") 30 break 31 else: 32 print("staff [%s] sees door got closed, swipping the card....." % n) 33 print(door_swiping_event.set()) 34 door_swiping_event.set() 35 print("after set ",door_swiping_event.set()) 36 time.sleep(0.5) 37 door_swiping_event = threading.Event() #设置事件 38 39 40 door_thread = threading.Thread(target=door) 41 door_thread.start() 42 43 44 45 for i in range(5): 46 p = threading.Thread(target=staff,args=(i,)) 47 time.sleep(random.randrange(3)) 48 p.start()
12.queue队列
队列在线程编程中尤其有用,因为信息必须在多个线程之间安全地交换。
class queue.Queue(maxsize = 0)#先入先出 (FIFO)
class queue.LifoQueue(maxsize = 0)#last in fisrt out
class queue.PriorityQueue(maxsize = 0)#存储数据时可设置优先级的队列
maxsize是一个整数,它设置了可以放入队列中的项目数量的上限。一旦达到此大小,插入就会阻塞,直到队列项被消耗。如果maxsize小于或等于零,队列大小是无限的。
首先检索最低值的条目(最低值的条目是由sorted(list(entries))[0]返回的条目。条目的典型模式是形式为 (priority_number, data)的元组。
异常queue.Empty
在非空的队列对象上调用非阻塞 get() (or get_nowait())时引发的异常。
异常队列
当非阻塞 put() (or put_nowait()) 在已满的Queue对象上调用时引发异常。
Queue.qsize()
Queue.empty()#return如果为空,则返回True
Queue.full() #return如果已满
Queue.put(item, block=True, timeout=None)
将项目放入队列。如果可选的args块为true并且超时为None(默认值),则在必要时阻塞,直到空闲插槽可用。如果timeout是一个正数,它会阻塞最多超时秒数,并且如果在该时间内没有可用空闲时间,则会引发完全异常。否则(块为假),如果空闲时隙立即可用,则将一个项目放在队列上,否则抛出完全异常(在这种情况下忽略超时)。
Queue.put_nowait(item)
相当于put(item,False)。
Queue.get(block=True, timeout=None)
从队列中删除并返回项目。如果可选的args块为true并且timeout为None(默认值),则在必要时阻止,直到项目可用。如果timeout是一个正数,它会阻塞最多超时秒数,并且如果在该时间内没有可用的项目,则会引发Empty异常。否则(块为假),返回一个项,如果一个立即可用,否则提出空异常(在这种情况下超时被忽略)。
Queue.get_nowait()
相当于get(False)。
提供了两种方法来支持跟踪入队任务是否已由守护程序消费者线程完全处理。
Queue.task_done()
指示以前入队的任务已完成。由队列消费者线程使用。对于用于获取任务的每个get(),对task_done()的后续调用将告诉队列任务上的处理已完成。
如果join()当前正在阻塞,它将在所有项目都被处理时恢复(意味着对于已经被put()到队列中的每个项目都接收到一个task_done()调用)。
如果调用的次数比在队列中放置的项目多,则引发ValueError。
Queue.join()block直到queue被消费完毕
13.生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
下面来看一个最基本的生产者消费者模型的例子
1 import threading 2 import queue 3 4 def producer(): 5 for i in range(10): 6 q.put("骨头 %s" % i ) 7 print("开始等待所有的骨头被取走...") 8 q.join() 9 print("所有的骨头被取完了...") 10 11 def consumer(n): 12 while q.qsize() >0: 13 print("%s 取到" %n , q.get()) 14 q.task_done() #告知这个任务执行完了 15 16 q = queue.Queue() 17 18 p = threading.Thread(target=producer,) 19 p.start() 20 21 c1 = consumer("李闯")
1 import time,random 2 import queue,threading 3 q = queue.Queue() 4 def Producer(name): 5 count = 0 6 while count <20: 7 time.sleep(random.randrange(3)) 8 q.put(count) 9 print(‘Producer %s has produced %s baozi..‘ %(name, count)) 10 count +=1 11 def Consumer(name): 12 count = 0 13 while count <20: 14 time.sleep(random.randrange(4)) 15 if not q.empty(): 16 data = q.get() 17 print(data) 18 print(‘\\033[32;1mConsumer %s has eat %s baozi...\\033[0m‘ %(name, data)) 19 else: 20 print("-----no baozi anymore----") 21 count +=1 22 p1 = threading.Thread(target=Producer, args=(‘A‘,)) 23 c1 = threading.Thread(target=Consumer, args=(‘B‘,)) 24 p1.start() 25 c1.start()
14.多进程multiprocessing
multiprocessing是一个包,它支持使用类似于线程模块的API来生成进程。 多进程包提供本地和远程并发,通过使用子进程而不是线程有效地避开全局解释器锁。 因此,多处理模块允许编程人员充分利用给定机器上的多个处理器。 它能在Unix和Windows上运行。
1 from multiprocessing import Process 2 import time 3 def f(name): 4 time.sleep(2) 5 print(‘hello‘, name) 6 7 if __name__ == ‘__main__‘: 8 p = Process(target=f, args=(‘bob‘,)) 9 p.start() 10 p.join()
要显示涉及的单个进程ID,下面是一个扩展示例:
1 from multiprocessing import Process 2 import os 3 4 def info(title): 5 print(title) 6 print(‘module name:‘, __name__) 7 print(‘parent process:‘, os.getppid()) 8 print(‘process id:‘, os.getpid()) 9 print("\\n\\n") 10 11 def f(name): 12 info(‘\\033[31;1mfunction f\\033[0m‘) 13 print(‘hello‘, name) 14 15 if __name__ == ‘__main__‘: 16 info(‘\\033[32;1mmain process line\\033[0m‘) 17 p = Process(target=f, args=(‘bob‘,)) 18 p.start() 19 p.join()
15.进程间通讯
不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:
Queues
使用方法跟threading里的queue差不多
1 from multiprocessing import Process, Queue 2 3 def f(q): 4 q.put([42, None, ‘hello‘]) 5 6 if __name__ == ‘__main__‘: 7 q = Queue() 8 p = Process(target=f, args=(q,)) 9 p.start() 10 print(q.get()) # prints "[42, None, ‘hello‘]" 11 p.join()
Pipes
Pipe()函数返回通过管道连接的一对连接对象,默认情况下是duplex (two-way)。 例如:
1 from multiprocessing import Process, Pipe 2 3 def f(conn): 4 conn.send([42, None, ‘hello‘]) 5 conn.close() 6 7 if __name__ == ‘__main__‘: 8 parent_conn, child_conn = Pipe() 9 p = Process(target=f, args=(child_conn,)) 10 p.start() 11 print(parent_conn.recv()) # prints "[42, None, ‘hello‘]" 12 p.join()
Pipe()返回的两个连接对象表示管道的两端。 每个连接对象都有send()和recv()方法(等等)。 请注意,如果两个进程(或线程)尝试同时读取或写入管道的同一端,则管道中的数据可能会损坏。 当然,同时使用管道的不同端的进程没有corruption(损坏的意思?)的风险。
16.Managers
Manager()返回的管理器对象控制一个保存Python对象的服务器进程,并允许其他进程使用代理来操作它们。
Manager()返回的管理器将支持类型列表,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value和Array。 例如,
1 from multiprocessing import Process, Manager 2 3 def f(d, l): 4 d[1] = ‘1‘ 5 d[‘2‘] = 2 6 d[0.25] = None 7 l.append(1) 8 print(l) 9 10 if __name__ == ‘__main__‘: 11 with Manager() as manager: 12 d = manager.dict() 13 14 l = manager.list(range(5)) 15 p_list = [] 16 for i in range(10): 17 p = Process(target=f, args=(d, l)) 18 p.start() 19 p_list.append(p) 20 for res in p_list: 21 res.join() 22 23 print(d) 24 print(l)
17.进程同步
Without using the lock output from the different processes is liable to get all mixed up.
没有使用进程同步的时候,输出的时候可能会导致输出混乱
1 from multiprocessing import Process, Lock 2 3 def f(l, i): 4 l.acquire() 5 try: 6 print(‘hello world‘, i) 7 finally: 8 l.release() 9 10 if __name__ == ‘__main__‘: 11 lock = Lock() 12 13 for num in range(10): 14 Process(target=f, args=(lock, num)).start()
18.进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。
进程池中有两个方法:
- apply
- apply_async 常用
1 from multiprocessing import Process,Pool 2 import time 3 4 def Foo(i): 5 time.sleep(2) 6 return i+100 7 8 def Bar(arg): 9 print(‘-->exec done:‘,arg) 10 11 pool = Pool(5) 12 13 for i in range(10): 14 pool.apply_async(func=Foo, args=(i,),callback=Bar) 15 #pool.apply(func=Foo, args=(i,)) 16 17 print(‘end‘) 18 pool.close() 19 pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
以上是关于Python程序笔记20230302的主要内容,如果未能解决你的问题,请参考以下文章