Python实践系列教程
基础知识
# 查看keyword列表
import keyword
print(keyword.kwlist)
"""
result = ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
"""
# 查看数据类型
num1 = 10
num2 = 3.14
str = "my name is wzx"
print(type(num1))
print(type(num2))
print(type(str))
"""
result:
<class 'int'>
<class 'float'>
<class 'str'>
"""
# 打印
name = "wzx"
print("my name is %s" % name)
print('my name is {}'.format(name))
print(f'my name is {name}')
print('abc\tABC')
print(r'abc\tABC')
"""
result:
my name is wzx
my name is wzx
my name is wzx
abc ABC
abc\tABC
"""
# note: %d %c %s %.4f(四位小数)
# /除 %取余 //取整除
# and or not 与 或 非/取反
# input
name = input('请输入用户名:')
age = input("请输入年龄:")
print(f'name:{name}, age:{age}')
print(type(name))
print(type(age))
"""
result:
请输入用户名:wzx
请输入年龄:88
name:wzx, age:88
<class 'str'>
<class 'str'>
"""
# if
age = input("请输入年龄:")
age = int(age)
if age > 0:
print(f"your age is {age}")
elif age > 140:
print("重新输入")
else:
print("重新输入")
"""
result:
请输入年龄:-1
重新输入
"""
# if elif else
while input() != "wzx":
print("登陆失败")
else:
print("登陆成功")
"""
result:
www
登陆失败
wzx
登陆成功
"""
# 循环
for i in range(1,6) # result 1-5
for i in range(100) # 0-99
# 总结
name = 'wzx'
pw = '123'
for i in range(3):
user = input("请输入用户名:")
passw = input("密码:")
if user==name and passw==pw:
print("登陆成功")
break
else:
print("登陆失败。。。。。。")
"""
result:
请输入用户名:wzx
密码:das
登陆失败。。。。。。
请输入用户名:wzx
密码:123
登陆成功
"""
列表
# 字符串是可迭代对象
name = "abcdef"
print(name[0])
print(name[-2])
print(name[3:])
print(name[3])
print(name[0:3])
print(name[2:4])
"""
result:
a
e
def
d
abc
cd
"""
print(name[0:5:2]) # 0 0+2 0+2+2 -> abc 5不取
print(name[5:0:-1]) # 5 5-1 5-1-1 ... ->fedcb 0不取
print(name[1:5:2]) # bd 5不取
print(name[0:2:-1]) # 无结果,并不会 0-1 然后从最后一位开始取
replace
# replace
name = "aabcdef"
print(name.replace('a', 'A'))
print(name) # 不会修改原有字符串
print(name.replace('a', 'A', 1)) # 只修改一次
"""
AAbcdef
aabcdef
Aabcdef
"""
slipt loewr and upper
say = 'My name is wzx'
print(say.split(" "))
print(say) # 不会修改原有字符串
"""
['My', 'name', 'is', 'wzx']
My name is wzx
"""
# loewr and upper
print(say.lower()) # 不会修改原有字符串
print(say.upper()) # 不会修改原有字符串
"""
my name is wzx
MY NAME IS WZX
"""
strip
str1 = " fuckfuck cao "
print(str1.strip())
"""fuckfuck cao"""
join
str_list = ["hello", "world"]
my_str = "_"
print(my_str.join(str_list))
"""
hello_world
"""
sort and reserve
int_data = [1, 1, 3, 4, 5, 5, 2, 2, 5]
int_data.sort()
print(int_data)
int_data.sort(reverse=True) # 反转
print(int_data)
int_data.reverse() # 再反转
print(int_data)
"""
[1, 1, 2, 2, 3, 4, 5, 5, 5]
[5, 5, 5, 4, 3, 2, 2, 1, 1]
[1, 1, 2, 2, 3, 4, 5, 5, 5]
"""
append
stu_information = ["wzx", 18, "b", '长沙', 98.5]
for information in stu_information:
print(information)
stu_information.append("sb")
print(stu_information)
"""
wzx
18
b
长沙
98.5
['wzx', 18, 'b', '长沙', 98.5, 'sb']
"""
inset
int_data = [1, 2, 5]
int_data.insert(2, 3) # index为2插入3
print(int_data)
int_data.insert(2, 4)
print(int_data)
"""
[1, 2, 3, 5]
[1, 2, 4, 3, 5]
"""
pop remove
int_data = [1, 1, 3, 4, 5, 5, 2, 2, 5]
del int_data[5]
print(int_data)
data = int_data.pop() # 默认从最后开始
print(int_data)
print(data)
int_data = [1, 1, 3, 4, 5, 5, 2, 2, 5]
data1 = int_data.pop(2) # delete which index is 2
print(int_data)
print(data1)
int_data.remove(4) # delete 4
print(int_data)
"""
[1, 1, 3, 4, 5, 2, 2, 5]
[1, 1, 3, 4, 5, 2, 2]
5
[1, 1, 4, 5, 5, 2, 2, 5]
3
[1, 1, 5, 5, 2, 2, 5]
"""
reserve
int_data = [1, 1, 3, 4, 5, 5, 2, 2, 5]
int_data.sort()
print(int_data)
int_data.sort(reverse=True) # 反转
print(int_data)
int_data.reverse() # 再反转
print(int_data)
"""
[1, 1, 2, 2, 3, 4, 5, 5, 5]
[5, 5, 5, 4, 3, 2, 2, 1, 1]
[1, 1, 2, 2, 3, 4, 5, 5, 5]
"""
总结
import random
offices = [[], [], []]
name_list = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K']
for name in name_list:
random_index = random.randint(0, 2)
offices[random_index].append(name)
i = 1
for office in offices:
print(f"编号为{i}的办公室人数为{len(office)}")
i += 1
for name in office:
print(name, end=" ")
print("\n")
"""
编号为1的办公室人数为5
B C D F I
----------------------------------------
编号为2的办公室人数为2
H J
----------------------------------------
编号为3的办公室人数为4
A E G K
----------------------------------------
"""
元组 集合
data = (1, 2, [3, 4, 5])
data_1 = (1)
data_2 = (1,)
print(type(data))
print(type(data_1))
print(type(data_2))
# data[1] = 6 报错,不能修改元组元素的地址
data[2][0] = 6 # 没有改变data[2]内存地址,可以修改列表中的值
print(data)
# 数据类型无法改变:元组 bool 字符串 数据类型(int float 复数)
"""
<class 'tuple'> 元组
<class 'int'>
<class 'tuple'>
(1, 2, [6, 4, 5])
"""
def get():
return 1, 2, 3
data = get()
print(data)
int_data = (i for i in range(5)) # 生成器对象
print(int_data)
print(list(int_data))
"""
(1, 2, 3)
<generator object <genexpr> at 0x000001F2071170D0>
[0, 1, 2, 3, 4] 列表
"""
# !!!! 集合只能储存可哈希对象,即都是不可改变对象
data = {1, 1, 1, 2, 3, 4}
print(type(data))
print(data) # 重复的会被省略
for i in data:
print(i) # 集合中的数据是无序的
# 相互转换去重
data_1 = [1, 1, 1, 1, 2, 3, 4, 5]
new_data = set(data_1)
print(list(new_data))
"""
<class 'set'>
{1, 2, 3, 4}
1
2
3
4
[1, 2, 3, 4, 5]
"""
data = {1, 2, 3, 4}
data.add(5)
print(data)
data.clear()
print(data)
del data
print(data)
"""
{1, 2, 3, 4, 5}
set()
NameError: name 'data' is not defined # 彻底删除
"""
data = {1, 2, 3, 4, 5}
da = data.pop() # 任意删除一个元素
print(data, da)
da1 = data.remove(5)
print(data, da1)
"""
{2, 3, 4, 5} 1
{2, 3, 4} None
"""
n1 = {1, 2}
n2 = {2, 3, 4}
print(n1 & n2) # 交集
print(n1 | n2) # 并集
print(n1 - n2) # 差集
print(n2 - n1)
"""
{2}
{1, 2, 3, 4}
{1}
{3, 4}
"""
字典
stu_info = {
'name': 'wzx',
'age': 18,
'address': 'shantou'
}
# print(stu_info['QQ']) 直接报错
print(stu_info.get('QQ'))
print(stu_info.get('QQ', '当前信息不存在......'))
print('-' * 20)
print(stu_info['name'])
print('-' * 20)
for i in stu_info:
print(i)
print('-' * 20)
for i in stu_info.values():
print(i)
print('-' * 20)
for i in stu_info.items():
print(i)
print('-' * 20)
for key, value in stu_info.items():
print(key, value)
print('-' * 20)
stu_info['sex'] = '男'
print(stu_info)
stu_info.clear()
print(stu_info)
del stu_info
print('-' * 20)
data = {}
data1 = list()
data2 = tuple()
data3 = set()
data4 = dict()
print(type(data), type(data1), type(data2), type(data3), type(data4))
"""
None
当前信息不存在......
--------------------
wzx
--------------------
name
age
address
--------------------
wzx
18
shantou
--------------------
('name', 'wzx')
('age', 18)
('address', 'shantou')
--------------------
name wzx
age 18
address shantou
--------------------
{'name': 'wzx', 'age': 18, 'address': 'shantou', 'sex': '男'}
{}
--------------------
<class 'dict'> <class 'list'> <class 'tuple'> <class 'set'> <class 'dict'>
"""
元组不可变,集合、字典、列表可以变
print(id(name)) # 获取地址
def test():
print("1")
def test2():
print("2")
test()
test2()
name = 2
print(id(test), id(test2), id(name))
test2 = test
test2()
print(id(test2))
"""
1
2
1862292422464 1862292013328
1
1862292422464dd
"""
列表、集合、字典推导式子
data = [index for index in range(1, 21) if index % 2 == 0]
print(data) # 列表、集合、字典推导式子
data1 = [(x, y) for x in range(2) for y in range(9) if y % 2 == 0]
print(data1)
a = [x for x in range(11)]
b = [a[x:x + 3] for x in range(0, len(a), 3)]
print(b)
print('-' * 20)
data2 = {x: x ** 2 for x in range(10)}
print(data2)
"""
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
[(0, 0), (0, 2), (0, 4), (0, 6), (0, 8), (1, 0), (1, 2), (1, 4), (1, 6), (1, 8)]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
--------------------
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
"""
打包 和 拆包
name = ['wzx', 'xxx', 'kkk']
name1, name2, name3 = name # 变量个数和元素个数必须一致
print(name1, name2, name3)
name = {'wzx', 'xxx', 'kkk'}
name1, name2, name3 = name
print(name1, name2, name3)
name = ('wzx', 'xxx', 'kkk')
name1, name2, name3 = name
print(name1, name2, name3)
info = {'name': 'wzx', 'age': 18}
name, age = info
print(name, age)
name, age = info.values()
print(name, age)
name, age = info.items()
print(name, age)
"""
wzx xxx kkk
xxx kkk wzx
wzx xxx kkk
name age
wzx 18
('name', 'wzx') ('age', 18)
"""
a = 4
b = 5
a, b = b, a
print(a, b)
"""
5 4 # 拆包,交换元组 4,5 -> 5, 4
"""
pass
pass的使用
def tai():
pass
for i in range(5):
pass
if 5 > 3:
pass
try:
pass
except:
pass
else:
pass
finally:
pass
全局和局部
def test():
num = 1
name = 'wzx'
info = [{'gender': '女'}, {'age': 18}]
return num, name, info
print("hello,you are sb")
data = test()
print(data)
print('__' * 20)
"""
(1, 'wzx', [{'gender': '女'}, {'age': 18}])
____________________________________________
"""
num, name, info = test()
print(num, name, info)
# def test1():
# print("无参数返回")
#
# def test2():
# return '无参数返回'
#
# def test3(num):
# print('返回值为', num)
#
# def test4(num):
# return f'返回值为:{num}'
num = 100
def test():
num = 666
def global_change():
global num # 声明全局变量在函数中访问并修改,用于不可变对象
num += 100
return num
test()
print(global_change())
"""
200
"""
def global_change():
global num
num = (1, 2)
return num, 2
num = (100, 99)
print(global_change())
"""
((1, 2), 2)
"""
函数参数顺序
def print_info(name, age=18): # 事先初始化的参数要放在后面比如age放在name后面
print(name, age)
print_info('wzx')
print_info('wzx', 100) # 事先初始化的参数要放在后面比如age放在name后面
"""
def print_info(name, age=18): # 事先初始化的参数要放在后面比如age放在name后面
print(name, age)
print_info('wzx')
print_info('wzx', 100) # 事先初始化的参数要放在后面比如age放在name后面
"""
不定长参数
def test(a, b, *args, **kwargs):
"""
:param a:
:param b:
:param args: # 不定长元组,可以不传递参数
:param kwargs: #不定长字典,可以不传递参数
:return:
"""
print(type(a), type(b), type(args), type(kwargs))
print(a, b, args, kwargs)
print('-' * 80)
test(3, 4)
test(1, 2, 3, 4, 5)
test(1, 2, 3, 4, 5, {'name' == 'wzx'})
test(1, 2, 3, 4, 5, {'name' == 'wzx'}, name='wzx', age=18)
"""
<class 'int'> <class 'int'> <class 'tuple'> <class 'dict'>
3 4 () {}
--------------------------------------------------------------------------------
<class 'int'> <class 'int'> <class 'tuple'> <class 'dict'>
1 2 (3, 4, 5) {}
--------------------------------------------------------------------------------
<class 'int'> <class 'int'> <class 'tuple'> <class 'dict'>
1 2 (3, 4, 5, {False}) {}
--------------------------------------------------------------------------------
<class 'int'> <class 'int'> <class 'tuple'> <class 'dict'>
1 2 (3, 4, 5, {False}) {'name': 'wzx', 'age': 18}
--------------------------------------------------------------------------------
"""
小整数对象池
# 提高内存的复用性
a = 10
b = 10
print(id(a), id(b)) # 小整数对象池[-5, 257],在ide3是这样,但在pycharm不是
c = 3001
d = 3001
print(id(c), id(d))
"""
2396267086352 2396267086352
2396271724560 2396271724560
"""
闭包
# 闭包
def test(a):
def wrapper():
print(a)
return wrapper
t = test(10)
t()
print(type(t))
"""
10
<class 'function'>
"""
字典推导式 和 sort函数
stu_list = [
{'name': 'wzx', 'age': 12},
{'name': 'wbb', 'age': 14},
{'name': 'waa', 'age': 11}
]
def sort_by_age(stu_list):
return stu_list['age']
sorted_data = sorted(stu_list, key=sort_by_age) # key接受函数地址
print(sorted_data)
stu_list.sort(key=lambda x: x['age'])
print(stu_list)
stu_list.sort(key=lambda x: x['name'])
print(stu_list)
num_list = [1, 4, 6, 2]
num_list.sort()
print(num_list)
"""
[{'name': 'waa', 'age': 11}, {'name': 'wzx', 'age': 12}, {'name': 'wbb', 'age': 14}]
[{'name': 'waa', 'age': 11}, {'name': 'wzx', 'age': 12}, {'name': 'wbb', 'age': 14}]
[{'name': 'waa', 'age': 11}, {'name': 'wbb', 'age': 14}, {'name': 'wzx', 'age': 12}]
[1, 2, 4, 6]
"""
def test():
num = 1
name = 'wzx'
info = [{'gender': '女'}, {'age': 18}]
return num, name, info
print("hello,you are sb")
data = test()
print(data)
print('__' * 20)
"""
(1, 'wzx', [{'gender': '女'}, {'age': 18}])
____________________________________________
"""
num, name, info = test()
print(num, name, info)
# def test1():
# print("无参数返回")
#
# def test2():
# return '无参数返回'
#
# def test3(num):
# print('返回值为', num)
#
# def test4(num):
# return f'返回值为:{num}'
num = 100
def test():
num = 666
def global_change():
global num # 声明全局变量在函数中访问并修改,用于不可变对象
num += 100
return num
test()
print(global_change())
"""
200
"""
def global_change():
global num
num = (1, 2)
return num, 2
num = (100, 99)
print(global_change())
"""
((1, 2), 2)
"""
学生信息查询系统
import os
info_list = list()
def print_menu():
print('--' * 20)
print("学生管理系统1.0")
print("1.添加学生\n2.删除学生\n3.修改学生\n4.查询学生\n5.显示学生\n6.退出系统")
print('--' * 20)
def new_info():
name = input("请输入名字:")
phone = input("请输入电话:")
qq = input("请输入QQ:")
for the_name in info_list:
if the_name['name'] == the_name:
print("该用户已经存在......")
return
info = dict()
info['name'] = name
info['phone'] = phone
info['QQ'] = qq
info_list.append(info)
print('添加成功')
def del_info():
name = input('请输入要删除学生的名字:')
for the_name in info_list:
if the_name['name'] == name:
info_list.remove(the_name)
print("删除成功")
return
else:
print("该学生不存在与列表中......")
return
def change_info():
name = input("请输入要修改学生的姓名:")
for stu_info in info_list:
if stu_info['name'] == name:
while True:
key = input(f"请输入{name}要修改的信息(name/phone/QQ):")
stu_info[key] = input(f"要将{name}的{key}修改为:")
continue_or_not = input("是否要修改其他参数,是请按Y,若不是请按N:")
if continue_or_not == 'Y':
continue
elif continue_or_not == 'N':
return
else:
print("输入有误,已经帮你自动退出")
return
else:
print("所查找的学生不存在......")
return
def search_info():
name = input("请输入要查找学生的姓名:")
for stu_info in info_list:
if stu_info["name"] == name:
print(f"该学生的名字是{stu_info['name']}")
print(f"该学生的电话是{stu_info['phone']}")
print(f"该学生的QQ是{stu_info['qq']}")
return
else:
print("所查找的学生不存在......")
return
def show_info():
for stu_info in info_list:
print(stu_info)
return
def main():
while True:
print_menu()
num = input("请输入要进行操作的数字:")
if num == '1':
new_info()
elif num == '2':
del_info()
elif num == '3':
change_info()
elif num == '4':
search_info()
elif num == '5':
show_info()
elif num == '6':
os.system("cls")
return 0
main()
python面向对象
入门
类example的创建、使用
example1
class Student:
def __init__(self): # self是创建对象本身
wuzhong = '人类' # 类属性,可以用Student.wuzhong直接访问
# 在创建对象的时候自动调用
print('这是一个构造方法')
# def __init__(self, name, gender, age):
# self.name = name
# self.gender = gender
# self.age = age
def set_student(self, gender, age, name='wzx'):
# 只有你使用这个方法的时候才自动调用
self.name = name
self.gender = gender
self.age = age
# 实例方法可以访问类中所有属性,而def do_homework(xxx): 这种方法不行
def do_homework(self):
print("正在写作业......")
@staticmethod # 静态方法,无法访问类中任何元素
def test(x):
print(x + "6666")
stu1 = Student() # 类对象实例化
stu1.set_student('男', 18)
stu1.address = '汕头'
print(stu1.name, stu1.gender, stu1.age, stu1.address)
stu1.do_homework()
Student.do_homework(stu1)
stu1.test("fuck")
Student.test("ccc")
"""
这是一个构造方法
wzx 男 18 汕头
正在写作业......
正在写作业......
fuck6666
ccc6666
"""
example2
class Dog:
def __init__(self, name, age, money):
self.name = name
self.__age = age
self.__money = money
def show_info(self):
print(f"name:{self.name}, age:{self.__age}")
def change_age(self, new):
if 1 <= new <= 40:
self.__age = new
else:
print("输入的年龄有误")
def __get_money(self): # 私有方法,在函数外部无法被调用
print(f"{self.name}的价格为{self.__money}")
def run(self):
self.__get_money()
dog1 = Dog("哈士奇", 4, 9000)
# print(dog1.__age) # 会报错'Dog' object has no attribute '__age'
dog1.__age = 66 # 没有权限修改私有属性
dog1.show_info()
dog1.change_age(66)
dog1.change_age(6)
dog1.show_info()
# 私有属性和私有方法只是在python中加了一个前缀而已 _Dog__get_money() _Dog__get_money _Dog__money
dog1.run()
dog1._Dog__get_money()
print(dog1._Dog__money)
"""
name:哈士奇, age:4
输入的年龄有误
name:哈士奇, age:6
哈士奇的价格为9000
哈士奇的价格为9000
9000
"""
example3
class Classroom:
def __init__(self, name):
self.name = name
self.stus = list()
def add_stu(self, name, num):
stu_dic = {
'name': name,
'num': num
}
self.stus.append(stu_dic)
class Students:
def __init__(self, name, num):
self.name = name
self.num = num
class_1 = Classroom('class1')
stu1 = Students('aaa', 1)
stu2 = Students('bbb', 2)
stu3 = Students('ccc', 3)
class_1.add_stu(stu1.name, stu1.num)
class_1.add_stu(stu2.name, stu2.num)
class_1.add_stu(stu3.name, stu3.num)
print(class_1.stus[0])
print(class_1.stus[1])
print(class_1.stus[2])
"""
{'name': 'aaa', 'num': 1}
{'name': 'bbb', 'num': 2}
{'name': 'ccc', 'num': 3}
"""
example4
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print('run......')
def __eat(self):
print('eat......')
def sleep(self):
print('sleep......')
# 多继承 class A(B,C)
class Dog(Animal): # 继承
def __init__(self, num, name, age):
super().__init__(name, age) # 继承Animal中的init
Dog.num = num
def tail_whip(self):
print("The tail wagging the dog......")
def stick_out_tougue(self):
print('The dog sticks out its tongue')
def sleep(self):
print("the dog is sleeping......")
dog = Dog(2, 'dodo', 18)
print(dog.num)
dog = Dog('dodo', 18)
dog.run()
dog.sleep()
dog.stick_out_tougue()
print(Dog.__mro__)
print(Dog.__init__)
print(Dog.__doc__)
print('--' * 40)
print(dir(dog))
"""
run......
the dog is sleeping......
The dog sticks out its tongue
(<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)
<function Animal.__init__ at 0x000002034D76A9E0>
None
--------------------------------------------------------------------------------
['_Animal__eat', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name', 'run', 'sleep', 'stick_out_tougue', 'tail_whip']
没有eat,因为私有属性无法被继承
"""
类列表,extend
extend只能加迭代类型
type在pyhton中创造了一切,type对象是个指向自己的指针
class Cat:
def say(self):
print('cat')
class Dog:
def say(self):
print('dog')
class Duck:
def say(self):
print('duck')
animals_list = [Cat, Dog, Duck]
for animal in animals_list:
animal().say()
list1 = ["1"]
list2 = (2, 3)
list3 = {4, 5}
list4 = {'name': 'wzx'}
data = 'abcd'
list1.extend(data)
list1.extend(list2)
list1.extend(list3)
list1.extend(list4)
print(list1)
"""
cat
dog
duck
['1', 'a', 'b', 'c', 'd', 2, 3, 4, 5, 'name']
"""
super方法
class Father1():
def play_game(self):
print('父类1中的方法')
class Father2():
def play_game(self):
print('父类2中的方法')
class Son(Father2, Father1):
def play_game(self):
super().play_game() # 从左到右遍历Father2, Father1
Father1().play_game()
son = Son()
son.play_game()
"""
父类2中的方法
父类1中的方法
"""
class Father():
def __init__(self, name, age):
self.name = name
self.age = age
# 数据模型:通过print打印一个类的实例对象会调用__str__方法
def __str__(self):
return "%s的年龄是%d" % (self.name, self.age)
class Son(Father):
def __init__(self, name, age, collage):
super().__init__(name, age) # 子类在实例化过程会运行父类构造函数
self.collage = collage
def __str__(self):
return "%s的年龄是%d,学历是%s" % (self.name, self.age, self.collage)
father = Father('父亲', 50)
print(father)
son = Son("儿子", 18, "大学")
print(son)
"""
父亲的年龄是50
儿子的年龄是18,学历是大学
"""
实例方法,类方法,静态方法
class Test:
name = "我是一个类属性"
num = 0
def __init__(self, key):
self.key = key
Test.num += 1
def test1(self):
print(f"key:{self.key}")
@staticmethod
def add(a, b):
print(f"a+b={a + b}")
@classmethod
def test2(cls):
"""
只能访问类属性
:return:
"""
print(f"cls including {cls.name}")
test = Test(key="我是一个key")
test.test1()
test.test2()
test.add(3, 4)
Test.test2()
test1 = Test(key="fuckfuck")
test2 = Test(key='miaomiaomiao')
print(Test.num)
print(test.num) # test.__class__.num
print(id(Test))
print(str(id(test)) + " ", str(id(test1)) + " ", id(test2))
print(str(id(test.__class__)) + " ", str(id(test1.__class__)) + " ", id(test2.__class__))
print(dir(test))
"""
key:我是一个key
cls including 我是一个类属性
a+b=7
cls including 我是一个类属性
3
3
1594627918096
1594626269936 1594625980784 1594624539712
1594627918096 1594627918096 1594627918096
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add', 'key', 'name', 'num', 'test1', 'test2']
"""
多继承(钻石继承,树状继承)
钻石继承
父类重复调用
class Father:
def __init__(self, name):
print("Father__init__被调用")
self.name = name
print("Father__init__结束调用")
class Son1:
def __init__(self, name, age):
print("Son1__init__被调用")
Father.__init__(self, name)
self.age = age
print("Son1__init__结束调用")
class Son2:
def __init__(self, name, gender):
print("Son2__init__被调用")
Father.__init__(self, name)
self.gender = gender
print("Son2__init__结束调用")
class Grandson:
def __init__(self, *, name, age, gender): # 必须用name=xxx的形式传递参数
print("grandson的init被调用")
Son1.__init__(self, name, age)
Son2.__init__(self, name, gender)
print('grandson__init__调用结束')
gs = Grandson(name="wzx", age=18, gender='男')
"""
grandson的init被调用
Son1__init__被调用
Father__init__被调用
Father__init__结束调用
Son1__init__结束调用
Son2__init__被调用
Father__init__被调用
Father__init__结束调用
Son2__init__结束调用
grandson__init__调用结束
"""
解决父类重复调用
class Father:
def __init__(self, name, *args, **kwargs):
print("Father__init__被调用")
self.name = name
print("Father__init__结束调用")
class Son1(Father):
def __init__(self, name, age, *args, **kwargs):
print("Son1__init__被调用")
# Father.__init__(self, name)
super().__init__(name, *args, **kwargs)
self.age = age
print("Son1__init__结束调用")
class Son2(Father):
def __init__(self, name, gender, *args, **kwargs):
print("Son2__init__被调用")
# Father.__init__(self, name)
super().__init__(name, *args, **kwargs)
self.gender = gender
print("Son2__init__结束调用")
class Grandson(Son1, Son2):
def __init__(self, *, name, age, gender): # 必须用name=xxx的形式传递参数
print("grandson的init被调用")
# Son1.__init__(self, name, age)
# Son2.__init__(self, name, gender)
super().__init__(name, age, gender)
print('grandson__init__调用结束')
gs = Grandson(name="wzx", age=18, gender='男')
print(Grandson.__mro__)
"""
grandson的init被调用
Son1__init__被调用
Son2__init__被调用
Father__init__被调用
Father__init__结束调用
Son2__init__结束调用
Son1__init__结束调用
grandson__init__调用结束
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Father'>, <class 'object'>)
"""
树状继承
class E:
def __init__(self):
print("E")
class D:
def __init__(self):
print("D")
class C(E):
def __init__(self):
super().__init__()
print("C")
class B(D):
def __init__(self):
super().__init__()
print("B")
class A(B, C):
def __init__(self):
super().__init__()
print("A")
a = A()
"""
D
B
A
"""
class E:
def __init__(self):
print("E")
class D:
def __init__(self):
print("D")
class C(E):
def __init__(self):
super().__init__()
print("C")
class B(D):
def __init__(self):
super().__init__()
print("B")
class A(B, C):
def __init__(self):
super().__init__()
C.__init__(self)
print("A")
a = A()
"""
D
B
E
C
A
"""
迭代
迭代对象于迭代器
from collections.abc import Iterable, Iterator
print(isinstance([], Iterable)) # True
print(isinstance({}, Iterable)) # True
class Test:
def __init__(self):
self.student_list = []
class Test1:
def __init__(self):
self.student_list = []
def __iter__(self):
pass
test = Test()
test1 = Test1()
# 查看对象中是否有__iter__就是迭代对象,但不一定是迭代器
# __iter__是迭代器协议,用于返回迭代器对象
print(isinstance(test, Iterable)) # False
print(isinstance(test1, Iterable)) # True
print(isinstance(test1, Iterator)) # False
nums = [5, 7, 3] # 列表只是迭代对象不是迭代器对象
iter_obj = iter(nums) # 返回一个迭代对象
print(next(iter_obj)) # 5
print(next(iter_obj)) # 7
print(next(iter_obj)) # 3
# print(next(iter_obj)) 第四次报错
"""
True
False
True
False
5
7
3
"""
迭代器的简单实现(一个元组)
from collections.abc import Iterable, Iterator
class MyIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration # 触发异常
else:
self.current += 1
return self.current - 1
test = MyIterator(0, 10)
for i in test:
print(i)
print(isinstance(test, Iterable)) # True
print(isinstance(test, Iterator)) # True
"""
0
1
2
3
4
5
6
7
8
9
True
True
"""
just迭代对象,并不是迭代器
from collections.abc import Iterable, Iterator
num1 = [1, 2, 3]
print(isinstance(num1, Iterable)) # True
print(isinstance(num1, Iterator)) # False
num2 = {4, 5, 6}
print(isinstance(num2, Iterable)) # True
print(isinstance(num2, Iterator)) # False
num3 = (7, 8, 9)
print(isinstance(num3, Iterable)) # True
print(isinstance(num3, Iterator)) # False
print(num1.__dir__())
"""
['__new__', '__repr__', '__hash__', '__getattribute__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__iter__', '__init__', '__len__', '__getitem__', '__setitem__', '__delitem__', '__add__', '__mul__', '__rmul__', '__contains__', '__iadd__', '__imul__', '__reversed__', '__sizeof__', 'clear', 'copy', 'append', 'insert', 'extend', 'pop', 'remove', 'index', 'count', 'reverse', 'sort', '__class_getitem__', '__doc__', '__str__', '__setattr__', '__delattr__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__dir__', '__class__']
这里面没有__next__,但用for进行遍历它会自动iter(可迭代对象)
"""
another 迭代器(for循环的原理)
from collections.abc import Iterable, Iterator
class MyIterator:
def __init__(self):
self.items = []
self.current = 0
def add(self, value):
self.items.append(value)
def __iter__(self):
# test = iter(test) # 迭代对象——》迭代器对象
# 用于返回一个迭代对象
return self
def __next__(self):
# 自定义迭代规则
if self.current < len(self.items):
item = self.items[self.current]
self.current += 2
return item
else:
raise StopIteration
test = MyIterator()
test.add("wzx")
test.add("www")
test.add("zzz")
test.add("xxx")
test.add("aaa")
# for 循环自动实现iter(迭代对象)
while True:
try:
print(next(test)) # wzx zzz aaa
except StopIteration:
print("迭代结束")
break
print(isinstance(test, Iterable)) # True
print(isinstance(test, Iterator)) # True
for i in test:
print(i)
"""
wzx
zzz
aaa
迭代结束
True
True
wzx
zzz
aaa
"""
一个学生管理系统的迭代版
class MyIterator:
def __init__(self):
self.items = []
self.current = 0
def add(self, *, name, age, gender):
value = {
'name': name,
'age': age,
'gender': gender
}
self.items.append(value)
def __iter__(self):
# test = iter(test) # 迭代对象——》迭代器对象
# 用于返回一个迭代对象
return self
def __next__(self):
# 自定义迭代规则
if self.current < len(self.items):
item = self.items[self.current]
self.current += 1
return item
else:
self.current = 0
raise StopIteration
test = MyIterator()
test.add(name='wzx', age=18, gender='男')
test.add(name='aaa', age=20, gender='男')
test.add(name='bbb', age=19, gender='女')
while True:
try:
print(next(test))
except StopIteration:
print("迭代结束")
break
"""
{'name': 'wzx', 'age': 18, 'gender': '男'}
{'name': 'aaa', 'age': 20, 'gender': '男'}
{'name': 'bbb', 'age': 19, 'gender': '女'}
迭代结束
"""
生成器
example
def my_range(num):
i = 0
while i < num:
print("迭代中...")
yield i # 解释器遇到yield回挂起任务,每一次迭代都在这里暂停
print("迭代结束...")
i += 4
obj = my_range(30)
print(next(obj))
print(next(obj))
print(hasattr(obj, '__iter__'))
print(hasattr(obj, '__next__'))
"""
迭代中...
0
迭代结束...
迭代中...
4
True
True
"""
send和close
def my_range(num):
i = 0
while i < num:
# print("迭代中...")
received = yield i # 解释器遇到yield回挂起任务,每一次迭代都在这里暂停,
# 下一次迭代从其下一行开始运行
if received == '你好':
print('hello')
# print("迭代结束...")
i += 4
obj = my_range(30)
print(next(obj)) # 迭代第一次不能直接send,要先next或者send(None)
print(obj.send("你好"))
print(next(obj))
print(hasattr(obj, '__iter__'))
print(hasattr(obj, '__next__'))
obj.close()
print(obj)
# print(obj.__next__()) 报错
"""
0
hello
4
8
True
True
<generator object my_range at 0x000002035BBD7060>
"""
元组表达式也是一个生成器
test = (x for x in range(1, 20) if x % 2 == 0)
print(type(test))
iter(test)
print(next(test))
test.send("你好")
print(next(test))
test.close()
"""
<class 'generator'>
2
6
"""
闭包
example
def user(name):
"""
user是一个闭包
:param name:
:return:
"""
def say(message):
print(f"{name}:{message}")
return say
p1 = user("wzx")
p2 = user("csy")
print(p1, p2)
p1("啦啦啦")
p2("乐")
user("cnm")("fuck fuck")
test = user
if id(test) == id(user):
print("test与user地址相等,test是user的引用")
"""
<function user.<locals>.say at 0x000001EBED77A9E0> <function user.<locals>.say at 0x000001EBED77BE20>
wzx:啦啦啦
csy:乐
cnm:fuck fuck
test与user地址相等,test是user的引用
"""
nonlocal
def first(start=0):
def add(num):
nonlocal start # 用于访问修改外层函数
start += num
return start
return add
test1 = first()
test2 = first(6)
print(test2(1))
print(test2(2))
print(test1(1))
"""
7
9
1
"""
切片的序列类
+ 和 += 的一个区别
data = [1, 2, 3]
# data_test = data + (1, 2, 3)
# 调用的是__add__方法
# TypeError: can only concatenate list (not "tuple") to list
data += (6, 6, 7) # 调用的是__iadd__方法,用的是extend,支持所以迭代对象
data += {(8, 8), 4}
data += {'name': 'wzx'}.items()
print(data)
"""
[1, 2, 3, 6, 6, 7, (8, 8), 4, ('name', 'wzx')]
"""
一个支持切片的序列类
# 创造一个支持切片的序列类
# 需要在类中实现一个序列协议
class Mylist:
def __init__(self, student_list: list) -> None:
# 指定student_list的类型为列表,默认返回值为None
self.student_list = student_list
def __getitem__(self, item): # 需要实现__getitem__
return self.student_list[item]
def __reversed__(self):
return reversed(self.student_list) # reserved的实现方法
test = Mylist([1, 2, 3, 4])
print(test[::-1])
print(test[0::2])
"""
[4, 3, 2, 1]
[1, 3]
"""
抽象基类
from abc import ABC, abstractmethod
class Father(ABC):
# 抽象基类
@abstractmethod
def run(self):
print("这是一个父类方法")
class Son(Father):
# 如果son中为实现抽象基类定义的方法会报错
# TypeError: Can't instantiate abstract class Son with abstract method run
# pass
def run(self):
print("这是一个子类方法")
test = Son()
test.run()
"""
这是一个子类方法
"""
融合继承
在 django rest framework 的经常遇到
"""
1. 类中的功能要保持单一
2. mixin不能继承任何类
3. 不能使用supper
"""
class Animal:
def __init__(self, name):
self.name = name
class RunMixin:
def run(self):
print(f"{self.name} is running")
class FlyMixin:
def fly(self):
print(f"{self.name} is flying")
class Duck(Animal, RunMixin, FlyMixin):
pass
test = Duck("yaya")
test.run()
test.fly()
"""
yaya is running
yaya is flying
"""
装饰器
初见装饰器
code
def bug_level(level):
def wrapper(function):
def func(*args, **kwargs):
print(f"debug等级{level},函数名称为{function.__name__}")
return function(*args, **kwargs)
return func
return wrapper
@bug_level("info")
def send_message(name, message):
print(f"{name}:{message}")
return 'successful'
print(send_message("wzx", "乐"))
"""
# 上面的代码等价于下面的代码
my_wrapper = bug_level("info")
my_func = my_wrapper(send_message)
print(my_func("wzx", "hello"))
"""
result
debug等级info,函数名称为send_message
wzx:乐
successful
用类重写装饰器
code
class Logging:
def __init__(self, level: 'info'):
self.level = level
def __call__(self, function):
def wrapper(*args, **kwargs):
print(f'debug等级:{self.level},函数的名称:{function.__name__}')
function(*args, **kwargs)
return wrapper
@Logging(level='error')
def send_message(name, message):
print(f'{name}:{message}')
send_message('wzx', "乐")
"""
# 上面的代码等价于下面的代码
log_obj = Logging()
my_func = log_obj.__call__(send_message)
my_func('wzx', "乐")
"""
result
debug等级:error,函数的名称:send_message
wzx:乐
一些常用装饰器
- @staticmethod
- @classmethod
- @abstractmethod
- @property 需要返回之,且被它装饰的方法不能有其他参数
异常处理
异常是python内部定义好的一些错误类,继承Exception这个错误基类
try:
print('测试代码...')
except:
print('出现异常...')
else:
print('如果当前程序没有出现任何异常的情况下,则执行当前语句...')
finally:
print('无论程序是否异常都执行改代码')
自定义异常
class Error(Exception):
def __str__(self):
# 用print驱动
return "password is error"
def __repr__(self):
# 终端交互使用
return "password is error"
try:
raise Error()
except Error as e:
print(e)
文件
- open(path,mode='w') # w a r w+
- close() #关闭
- readline()返回一行数据 readlines()返回一个列表
- write()写入一行数据 writelines()写入一个列表
with上下文管理器
a example
class FileControl:
def __enter__(self):
print('开始调用')
def __exit__(self, exc_type, exc_val, exc_tb):
print('结束调用')
with FileControl() as file_obj:
pass
result:
开始调用
结束调用
another example
class OpenFile:
def __init__(self):
print("1")
self.file_obj = None
def __enter__(self):
print('2')
self.file_obj = open('test.txt')
return self
def my_read(self):
print(self.file_obj.read())
def __exit__(self, exc_type, exc_val, exc_tb):
print(3)
self.file_obj.close()
with OpenFile() as e:
e.my_read()
result:
1
2
aaaaa
bbbbb
ccccc
3
线程
原理
- 栈:每个线程都有自己的栈,用于存储局部变量和函数调用的信息。栈是线程的私有空间,用于管理线程的函数调用和局部变量等。
- 寄存器:线程的执行需要使用寄存器来存储和操作数据。通常,线程会使用一组寄存器来记录当前执行的上下文信息,包括程序计数器、数据寄存器、状态寄存器等。
- 上下文切换:上下文切换是指从一个线程切换到另一个线程的过程。当操作系统内核决定将处理器的控制权从当前线程切换到另一个线程时,它会保存当前线程的上下文信息(如寄存器状态)并将其存储到进程控制块(PCB)中。然后,它会加载待切换线程的上下文信息,使之能够继续执行。
- 进程控制块(PCB):PCB是操作系统内核用于管理进程和线程的数据结构,用于存储进程/线程的状态和相关信息,如程序计数器、寄存器状态、堆栈指针等。当线程切换发生时,上下文信息被保存到当前线程的PCB中,并加载下一个线程的上下文信息。
- 调度器:调度器是操作系统内核的组件,负责决定线程在处理器上的执行顺序。调度器使用调度算法来选择下一个要执行的线程,并将处理器时间片分配给它。调度算法的选择会影响线程的调度行为和性能。
- 同步机制:在多线程编程中,线程之间可能会访问共享资源,并发执行可能引发数据竞争和不一致性。为了确保数据一致性和线程安全,需要使用同步机制,如互斥锁、条件变量、信号量等。这些机制通过原子操作和内存屏障等手段来协调线程的访问。
入门
import time
import threading
def work1():
for i in range(5):
print("work1:", i)
time.sleep(0.5)
def work2():
for i in range(5):
print("work2:", i)
time.sleep(0.5)
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
t1.start()
t2.start()
result:
work1: 0
work2: 0
work2: 1
work1: 1
work2: 2
work1: 2
work2: 3
work1: 3
work2: 4
work1: 4
多线程爬取
import threading
import requests
def get_image(url):
# 子线程任务
response = requests.get(url).content
file_name = 'image/' + url.rsplit('/')[-1]
with open(file_name, 'wb') as f:
f.write(response)
print('下载完成...')
# 主线程
url_list = [
'http://pic.bizhi360.com/bbpic/98/10798.jpg',
'http://pic.bizhi360.com/bbpic/92/10792.ipg',
'http://pic.bizhi360.com/bbpic/86/10386.ipg'
]
# for url in url_list:
# get_image(url)
for url in url_list:
t = threading.Thread(target=get_image, args=(url,))
t.start() # 主线程创建子线程
GIL锁
GIL (Global Interpreter Lock) 是在 CPython 解释器中使用的一种机制。它是一种互斥锁,用于控制在同一时间只能有一个线程执行 Python 字节码指令的访问。 CPython 是 Python 的官方解释器实现,它使用 GIL 来确保同一时刻只有一个线程执行 Python 代码。这意味着在多线程的情况下,虽然有多个线程在运行,但它们无法真正地并行执行 Python 代码。只有等一个线程执行完毕后,其他线程才能开始执行。 这是因为 CPython 解释器的内部数据结构和全局状态是非线程安全的。GIL 的存在保护了这些非线程安全的部分,避免了出现竞态条件(race condition)和其他潜在的线程安全问题。 然而,GIL 也带来了一些影响:
-
阻碍了多线程的并行执行:当使用 CPU 密集型任务时,由于 GIL 的存在,多线程并不能真正利用多核处理器的所有核心进行并行计算。只有使用 IO 密集型任务,例如网络请求或磁盘读写时,才能在等待 IO 的过程中释放 GIL,让其他线程获得执行机会。
-
影响了多线程性能:在多线程环境下,GIL 引入了一些额外的开销,例如线程上下文切换的成本和锁竞争的开销。这可能导致多线程程序的性能不如预期。
需要注意的是,GIL 只存在于 CPython 解释器中,其他一些 Python 解释器,如 Jython 和 IronPython,并不具备 GIL。此外,在使用 CPython 解释器时,对于 CPU 密集型任务,可以考虑使用多进程代替多线程来充分利用多核处理器的能力。 总结起来,GIL 是 CPython 解释器中的一种机制,用于控制同一时间只能有一个线程执行 Python 字节码指令。它对于多线程并行执行有一些限制,但在某些特定情况下,如 IO 密集型任务,仍然可以在多线程环境中发挥一定的优势。
主线程与子线程
在 Python 中,主线程和子线程是多线程编程中的概念。
主线程是程序启动时自动创建的线程,它负责执行程序的主要逻辑。主线程负责调度和管理其他子线程,并且在主线程运行期间,它可以创建和销毁其他子线程。
子线程是由主线程创建的额外线程,用于执行一些辅助任务。创建子线程可以提高程序的并发性和响应性。子线程可以执行独立的任务,它们可以单独执行,同时与主线程并发运行。
主线程和子线程之间存在一定的关系:
- 启动子线程:主线程可以创建和启动子线程,通常使用 threading 模块或基于 threading 模块的高级库来实现。主线程可以调用
threading.Thread
类来创建子线程对象,并调用start()
方法启动子线程的执行。 - 子线程执行独立任务:一旦启动,子线程将在自己的执行上下文中执行独立的任务或函数。子线程可以与主线程同时运行,彼此之间不会阻塞或干扰。
- 主线程与子线程之间的协同:主线程可以与子线程进行协同。主线程可以等待子线程的完成,使用
join()
方法等待子线程执行结束。主线程还可以通过共享变量或队列等机制与子线程进行通信,进行数据共享或协作。 - 子线程的生命周期:子线程的生命周期不受主线程的影响。主线程可以继续执行其他任务,而不必等待子线程的完成。
需要注意的是,主线程的结束不会自动终止所有子线程。如果在程序中启动了子线程,当主线程完成时,如果仍然有子线程在运行,则程序将继续执行直到所有子线程完成或手动终止子线程。
线程方法
- threading.Thread() 创建一个线程对象。
start()
:启动线程,使其开始执行。join(timeout=None)
:阻塞调用线程,直到线程结束或者超时。timeout
参数指定等待时间,如果为None
,则表示一直等待直到线程结束。is_alive()
:返回线程是否还在运行,如果线程尚未启动或者已经结束,返回False
,否则返回True
。name
:线程的名字属性,可以通过thread.name
或者threading.current_thread().name
来获取线程的名字。ident
:线程的标识符属性,可以通过thread.ident
或者threading.current_thread().ident
来获取线程的标识符。daemon
:线程的守护属性,守护线程会在主线程结束时自动退出。可以通过thread.daemon
来获取或者设置线程的守护属性。run()
:线程执行的入口方法,可以通过继承threading.Thread
类,并重写run
方法来指定线程要执行的代码。
守护线程
守护线程是一种特殊类型的线程,它的生命周期与主线程或其他非守护线程的生命周期没有直接的关联。当所有的非守护线程结束时,守护线程会自动终止,并且不会阻止程序的退出。
守护线程的主要特点是:
- 生命周期不受其他线程的影响:守护线程的生命周期与主线程或其他非守护线程的生命周期独立。当所有非守护线程完成时,守护线程将自动终止,而不管它是否已经完成或等待任务。
- 在程序退出时自动终止:守护线程会在程序退出时自动终止,无论它是否完成了任务。这对于后台运行一些需要持续运行的任务非常有用,而不需要手动停止线程。
在 Python 中,可以使用 threading.Thread
的 daemon
属性将线程设置为守护线程。默认情况下,线程对象的 daemon
属性为 False
,表示该线程是非守护线程。如果将 daemon
设置为 True
,则该线程为守护线程。
守护线程的应用场景包括:
- 日志记录:使用守护线程在后台定期记录日志,而不需要主线程等待日志记录的完成。
- 后台任务:守护线程可用于执行一些后台任务,如定时清理临时文件或执行系统维护任务等。
- 资源管理:在某些情况下,如果非守护线程结束后需要释放一些资源,可以使用守护线程监控资源状态并及时释放。
需要注意的是,守护线程通常不适合执行一些需要确保完整性和一致性的任务,因为它们可能在任何时候中断。
example
import threading
import time
def daemon_thread():
print("守护线程开始")
time.sleep(3)
print("守护线程结束")
# 创建守护线程
daemon = threading.Thread(target=daemon_thread)
daemon.daemon = True # 将线程设置为守护线程
# daemon.setDaemon(True)
# 启动守护线程
daemon.start()
# 主线程等待一会儿后退出
time.sleep(1)
print("主线程结束")=
程序中将守护线程设置为守护线程,然后启动它。主线程在休眠 1 秒后退出。因为守护线程被设置为守护线程,主线程结束后,守护线程自动终止,不需要等待 3 秒的休眠结束。
python中线程的命名
import threading
def work():
print(threading.current_thread().name)
def main():
threads = []
for i in range(3):
t = threading.Thread(target=work)
t.start()
threads.append(t)
for t in threads:
t.join() # 等待子线程完成
print(threading.current_thread().name)
main()
result:
Thread-1 (work)
Thread-2 (work)
Thread-3 (work)
MainThread
多线程爬虫(用类实现)
import threading
import requests
class ThreadSpider(threading.Thread):
def __init__(self, image_url):
super.__init__()
self.image_url = image_url
def run(self) -> None:
response = requests.get(url=self.image_url).content
filename = self.image_url.rsplit('/')[-1]
with open("./image/" + filename, 'wb') as f:
f.write(response)
print('Download Successfully')
url_list = [
'http://pic.bizhi360.com/bbpic/98/10798.jpg',
'http://pic.bizhi360.com/bbpic/92/10792.ipg',
'http://pic.bizhi360.com/bbpic/86/10386.ipg'
]
for url in url_list:
t = ThreadSpider(url)
if name == 'main'
if __name__ == "__main__"
是一个常见的 Python 代码块,用于执行一些在当前脚本文件被直接运行时而不是被导入时应该执行的代码。
当一个 Python 脚本文件被直接运行时,Python 解释器会将其作为主模块进行执行。而当一个 Python 脚本文件被作为模块被导入时,Python 解释器会将其作为一个普通模块进行执行。
这个代码块的作用是为了区分当前脚本是被直接运行还是被导入,并在不同情况下执行不同的代码逻辑。
常见的用法包括:
- 主程序逻辑:在
if __name__ == "__main__"
代码块中编写主程序逻辑,这样当脚本文件被直接运行时,主程序逻辑会被执行。而当脚本文件被导入时,主程序逻辑不会被执行。 - 模块测试代码:在
if __name__ == "__main__"
代码块中编写一些测试代码,用于测试当前模块的功能。这样当模块被导入时,测试代码不会执行,但是当模块被直接运行时,测试代码会被执行。
example
# module.py
def add(a, b):
return a + b
if __name__ == "__main__":
# 当脚本文件被直接运行时执行以下代码
result = add(2, 3)
print(result)
当我们在终端中直接运行 python module.py
时,会输出 5
,而如果我们在另一个脚本文件中导入了 module
模块并调用 add
函数,则不会输出任何东西。这是因为主程序逻辑只有在脚本文件直接运行时才会被执行。
线程安全
线程安全(Thread Safety)是指在多线程环境下,多个线程同时访问共享资源时,不会出现意外的错误或者不一致的结果。在并发编程中,线程安全是一个重要的概念,因为多个线程之间的竞争条件会导致数据的不一致性和错误的结果。
线程安全是由于多个线程访问和修改共享资源可能会导致以下问题:
- 竞争条件(Race Condition):多个线程同时对一个共享资源进行读写操作,造成数据不一致的结果。
- 死锁(Deadlock):多个线程彼此持有某个资源的锁,并且相互等待对方释放锁,导致所有线程都无法继续执行。
- 活锁(Livelock):多个线程在试图解决死锁问题时,彼此礼让,无法继续执行。
为了保证线程安全,可以采取以下措施:
- 互斥锁(Mutex):通过加锁机制来保证同一时间只有一个线程可以访问共享资源,其他线程需要等待锁释放后才能访问。
- 信号量(Semaphore):通过一个计数器来控制同时访问共享资源的线程数量。当计数器为0时,表示资源被占用,其他线程需要等待。
- 条件变量(Condition):通过条件等待和条件通知机制来控制线程的执行顺序,确保线程在满足特定条件之后再进行操作。
- 原子操作(Atomic Operation):提供原子性的操作方法,能够在不需要加锁的情况下完成对共享资源的操作。
- 编写线程安全的数据结构和算法:设计数据结构和算法时考虑线程安全性,避免竞争条件和数据不一致。
需要注意的是,并不是所有的代码都需要进行线程安全处理,只有在多个线程同时访问和修改共享资源时才需要考虑线程安全性。如果只有一个线程在访问共享资源,那么就不存在线程安全问题。
互斥锁
尽管在你的代码中没有使用互斥锁进行同步控制,但是在某些情况下,你可能会得到正确的结果。这是因为在这个特定的案例中,共享资源shared_resource
的增加操作足够简单,不涉及到多个线程同时修改同一个数据的复杂情况,所以可能不会出现竞态条件。
然而,不加锁的情况下存在潜在的竞争条件,这会导致数据不一致的结果。当多个线程同时对shared_resource
进行写操作时,可能会发生数据覆盖或者读取脏数据的问题。
尽管某些情况下代码的输出结果是正确的,这并不代表该代码是正确的。在并发编程中,正确性不应该依赖于线程执行的顺序和时间。如果在不使用互斥锁的情况下,线程执行的顺序或时间发生变化,结果可能会变得不正确。
为了确保线程安全性和正确性,建议在涉及共享资源的并发代码中使用适当的同步机制,如互斥锁。通过上锁和解锁操作,可以保证每次只有一个线程可以进入临界区,避免竞态条件和数据不一致问题的发生。
import threading
# 共享资源
shared_resource = 0
# 互斥锁
# lock = threading.Lock() Lock不支持递归,无法同时上多把锁
lock = threading.RLock() # RLock可以上多把锁
# 在Python的threading模块中,RLock(递归锁)和Lock(互斥锁)都支持上下文管理协议(Context Management Protocol),即支持使用with语句来自动获取和释放锁。
# 计算函数
def calculate():
global shared_resource
for _ in range(10000):
lock.acquire() # 上锁
shared_resource += 1
lock.release() # 解锁
# 临界区
# with lock:
# shared_resource += 1
# 创建多个线程
threads = []
for _ in range(9):
thread = threading.Thread(target=calculate)
threads.append(thread)
# 启动线程
for thread in threads:
thread.start()
# 等待线程结束
for thread in threads:
thread.join()
# 输出结果
print(f"Final value of shared_resource: {shared_resource}")
# result:Final value of shared_resource: 90000
上多把锁的作用
在并发编程中,使用单个锁来保护共享资源可能是不够的,有时可能需要使用多把锁来实现更精细的同步操作。下面是多把锁的一些作用:
1.保护不同的临界区:
如果一个文件中有多个独立的临界区,可以为每个临界区分配一个独立的锁。这样可以确保在某一时刻只有一个线程能够访问每个临界区的代码,避免了临界区之间的竞态条件。
example:
import threading
class File:
def __init__(self):
self.data1 = []
self.data2 = []
self.lock1 = threading.Lock()
self.lock2 = threading.Lock()
def process_data1(self, new_data):
self.lock1.acquire() # 获取锁1
try:
# 对data1进行操作
self.data1.append(new_data)
print("Data1:", self.data1)
finally:
self.lock1.release() # 释放锁1
def process_data2(self, new_data):
self.lock2.acquire() # 获取锁2
try:
# 对data2进行操作
self.data2.append(new_data)
print("Data2:", self.data2)
finally:
self.lock2.release() # 释放锁2
# 创建文件对象
file = File()
# 创建多个线程进行操作1
for i in range(5):
threading.Thread(target=file.process_data1, args=(i,)).start()
# 创建多个线程进行操作2
for i in range(5):
threading.Thread(target=file.process_data2, args=(i,)).start()
在上述代码中,File
类表示一个文件对象,它包含两个数据列表 data1
和 data2
,以及两个锁 lock1
和 lock2
。
process_data1
方法用于处理 data1
,它在操作 data1
时获取锁1,执行操作后释放锁1。类似地,process_data2
方法用于处理 data2
,它在操作 data2
时获取锁2,执行操作后释放锁2。
在主线程中,我们创建了多个线程分别来调用 process_data1
和 process_data2
方法。这些线程并发执行,但是对于 data1
和 data2
的操作是互斥的,因为它们使用了不同的锁。
通过使用不同的锁来保护不同的临界区,我们可以实现对不同资源的独立控制,避免竞争条件和数据一致性问题。这样可以提高并发性能,并确保不同临界区之间的操作是安全的。
2.减少锁竞争:
使用多把锁可以减少不同线程之间对同一锁的竞争。如果多个线程只会访问其中一部分共享资源,可以为每个部分分配一个独立的锁。这样,不同的线程可以并发地访问不同的资源,减少了锁的争用,提高了并行性。
3.提高并发性能:
使用多把锁可以提高程序的并发性能。通过细粒度的锁粒度,可以让多个线程同时访问和修改不同的资源,从而提高并行性和整体性能。
4.避免死锁:
在使用多把锁的情况下,可以更好地规划锁的获取和释放顺序,避免死锁的发生。通过合理地组织锁的获取和释放,可以最小化死锁的可能性。
死锁的例子:
import threading
# 共享资源
shared_resource = 0
lock = threading.Lock()
# 计算函数
def calculate():
global shared_resource
for _ in range(10000):
lock.acquire() # 上锁
lock.acquire() # 普通锁上锁后必须解锁,不然再一次尝试上锁会一直等待解锁,但又无法走到下一步解锁,导致死锁
shared_resource += 1
lock.release() # 解锁
lock.release()
threads = []
for _ in range(9):
thread = threading.Thread(target=calculate)
threads.append(thread)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(f"Final value of shared_resource: {shared_resource}")
多个锁对象也会在不经意间导致死锁,所以全局公用一个锁对象
5.分离读写操作:
如果一个文件同时面临读和写的操作,可以使用读写锁(也称为共享-排它锁)来实现读写操作的分离。读写锁允许多个线程同时对共享资源进行读取,但只有一个线程能够对共享资源进行写操作。
example:
import threading
class File:
def __init__(self):
self.data = []
self.lock = threading.RWLock()
def read_data(self):
self.lock.acquire_read() # 获取读锁
try:
# 读取数据
print("Reading data:", self.data)
finally:
self.lock.release_read() # 释放读锁
def write_data(self, new_data):
self.lock.acquire_write() # 获取写锁
try:
# 修改数据
self.data = new_data
print("Writing data:", self.data)
finally:
self.lock.release_write() # 释放写锁
# 创建文件对象1热武器34我却2我却212121221212121212请问111·
file = File()
# 创建多个线程进行读操作
for _ in range(3):
threading.Thread(target=file.read_data).start()
# 创建一个线程进行写操作
threading.Thread(target=file.write_data, args=([1, 2, 3],)).start()
在上述代码中,File
类表示一个文件对象,包含了一个数据列表 data
和一个读写锁 lock
。read_data
方法使用读锁来读取数据,而 write_data
方法使用写锁来修改数据。
在主线程中,我们创建了多个读线程和一个写线程。读线程通过调用 file.read_data()
方法来读取数据,而写线程通过调用 file.write_data()
方法来修改数据。
由于读锁是共享锁,多个线程可以同时持有读锁,实现并发读操作。而写锁是排它锁,一次只能有一个线程持有写锁,实现互斥的写操作。
通过读写锁的使用,我们可以实现对文件对象的读写操作的分离,从而提高并发性能,并保证数据的一致性。
总之,使用多把锁可以更精细地控制并发操作,保护不同的临界区,减少锁竞争,并提高并发性能。同时,良好地使用多把锁可以避免死锁,并实现读写操作的分离。然而,使用多把锁也要谨慎,需要合理规划锁的获取和释放顺序,以避免死锁和其他并发问题。
线程池
入门
import concurrent.futures
# 定义一个任务函数
def task(num):
print(f"Executing task {num}")
return num * 2
# 创建线程池对象
with concurrent.futures.ThreadPoolExecutor() as executor:
# 提交多个任务给线程池执行
results = [executor.submit(task, i) for i in range(5)]
# 获取任务的结果
for future in concurrent.futures.as_completed(results):
result = future.result()
print(f"Task result: {result},is the task finished: {future.done()}")
Executing task 0
Executing task 1
Executing task 2
Executing task 3
Executing task 4
Task result: 8,is the task finished: True
Task result: 0,is the task finished: True
Task result: 6,is the task finished: True
Task result: 4,is the task finished: True
Task result: 2,is the task finished: True
当使用concurrent.futures
模块来管理线程池和执行异步任务时,以下是对每个方法的详细解释和示例:
-
ThreadPoolExecutor(max_workers=None)
:- 创建一个线程池对象。
max_workers
参数指定线程池中的最大线程数,默认为None
,表示自动根据系统情况确定线程数。- 示例:
executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
-
submit(fn, *args, **kwargs)
:- 将一个任务提交给线程池执行,并返回一个
Future
对象,用于获取任务的结果。 fn
是要执行的任务函数,args
和kwargs
是传递给任务函数的参数。- 示例:
def task(arg1, arg2): # 执行操作... return result future = executor.submit(task, arg1, arg2) result = future.result() # 获取任务的结果
- 将一个任务提交给线程池执行,并返回一个
-
map(fn, *iterables, timeout=None, chunksize=1)
:- 将多个任务提交给线程池执行,并返回一个结果生成器,用于迭代获取任务的结果。
fn
是要执行的任务函数,iterables
是传递给任务函数的可迭代对象,可以有多个。timeout
参数指定每个任务的最大超时时间,默认为None
,表示没有超时限制。chunksize
参数指定每次传递给任务函数的数据块大小,默认为1。- 示例:
def task(arg): # 执行操作... return result args = [arg1, arg2, arg3, arg4] results = executor.map(task, args) for result in results: # 处理结果...
-
as_completed(futures, timeout=None)
:- 接收一个
Future
对象的列表,并返回一个迭代器,用于获取已完成的任务的结果。 futures
参数是要监视的Future
对象列表。timeout
参数指定获取结果的超时时间,默认为None
,表示没有超时限制。- 示例:
futures = [executor.submit(task, arg1), executor.submit(task, arg2)] for future in concurrent.futures.as_completed(futures): result = future.result() # 处理已完成任务的结果
- 接收一个
-
shutdown(wait=True)
:- 优雅地关闭线程池,并等待所有任务完成。
wait
参数确定是否等待所有任务完成后再关闭线程池,默认为True
。- 示例:
executor.shutdown()
进程
进程原理
-
进程标识符(PID):每个进程都有一个唯一的标识符,用于在操作系统中进行标识和管理。
-
创建进程:操作系统提供了创建新进程的机制,通常通过调用系统调用来实现,例如fork()(在Unix-like系统中)或CreateProcess()(在Windows系统中)。
-
进程状态:进程可以处于运行、就绪、等待等不同的状态。操作系统会根据进程的状态和调度算法来确定哪个进程可以被执行。
-
进程间通信(IPC):不同的进程可以通过进程间通信机制来相互交换数据和信息。常见的IPC机制包括管道、共享内存、消息队列和套接字等。
-
进程调度:操作系统负责管理多个进程的执行顺序,以便合理利用系统资源和提高系统的吞吐量和响应速度。
-
终止进程:进程在完成任务后会被终止,或者在出现错误或异常情况下被操作系统终止。
-
资源管理:操作系统负责分配和管理进程所需的资源,例如内存、CPU时间等。
进程是实现并发和并行编程的一种重要方式。在应用程序中,你可以利用多进程来同时执行多个任务,以提高程序的性能和响应能力。
multiprocessing
以下是multiprocessing
模块中所有常用函数的详细解释:
-
multiprocessing.Process(target, args=(), kwargs={})
:- 创建一个新的进程对象。
target
参数指定进程要执行的目标函数。args
和kwargs
用于传递给目标函数的参数。
-
multiprocessing.current_process()
:- 返回当前进程的进程对象。
-
multiprocessing.active_children()
:- 返回一个正在运行的子进程的列表。
-
multiprocessing.cpu_count()
:- 返回当前系统的CPU核心数。
-
multiprocessing.current_process().name
:- 返回当前进程的名称。
-
multiprocessing.current_process().pid
:- 返回当前进程的进程ID。
-
multiprocessing.Queue(maxsize=0)
:- 创建一个进程安全的队列对象。
maxsize
参数指定队列的最大容量。
-
multiprocessing.Pool(processes=None)
:- 创建一个进程池对象。
processes
参数指定进程池中的进程数,默认为None
,表示根据系统情况确定进程数。
-
pool.apply(func, args=(), kwargs={})
:- 在进程池中同步执行一个任务,并返回结果。
func
参数指定要执行的函数。args
和kwargs
用于传递给函数的参数。
-
pool.map(func, iterable)
:- 在进程池中并发地执行多个任务,并返回结果列表。
func
参数指定要执行的函数。iterable
是一个可迭代对象,包含了传递给函数的参数。
-
pool.close()
:- 关闭进程池,不再接受新的任务。
-
pool.join()
:- 阻塞主进程,等待所有任务完成。
-
multiprocessing.Lock()
:- 创建一个进程锁对象,用于同步进程之间的访问。
-
multiprocessing.Event()
:- 创建一个进程事件对象,用于进程间的事件通知。
-
multiprocessing.Condition()
:- 创建一个进程条件对象,用于进程间的线程通信。
-
multiprocessing.Semaphore(value=1)
:- 创建一个进程信号量对象,用于控制同时访问资源的进程数量。
value
参数指定初始的信号量值。
-
multiprocessing.Pipe()
:- 创建一个双向管道对象,用于进程间的双向通信。
multiprocessing example
-
multiprocessing.Process(target, args=(), kwargs={})
:- 创建一个新的进程对象。
target
参数指定进程要执行的目标函数。args
和kwargs
用于传递给目标函数的参数。- 示例:
import multiprocessing def worker(num): # 执行操作... print(f"Worker: {num}") process = multiprocessing.Process(target=worker, args=(1,)) process.start() # 启动进程
-
multiprocessing.Pool(processes=None)
:- 创建一个进程池对象。
processes
参数指定进程池中的进程数,默认为None
,表示根据系统情况确定进程数。- 示例:
import multiprocessing def worker(num): # 执行操作... return f"Worker: {num}" pool = multiprocessing.Pool(processes=3) result = pool.apply(worker, (1,)) # 同步执行任务 print(result)
-
apply(func, args=(), kwargs={})
:- 在进程池中同步执行一个任务,并返回结果。
func
参数指定要执行的函数。args
和kwargs
用于传递给函数的参数。- 示例:
result = pool.apply(worker, (2,)) print(result)
-
map(func, iterable)
:- 在进程池中并发地执行多个任务,并返回结果列表。
func
参数指定要执行的函数。iterable
是一个可迭代对象,包含了传递给函数的参数。- 示例:
tasks = [1, 2, 3, 4, 5] results = pool.map(worker, tasks) print(results)
-
close()
:- 关闭进程池,不再接受新的任务。
- 示例:
pool.close()
-
join()
:- 阻塞主进程,等待所有任务完成。
- 示例:
pool.join()
OS
以下是
os
模块中所有常用函数的详细解释:-
os.name
:- 获取当前操作系统的名称。
- 返回值为
posix
(Unix/Linux/Mac OS),nt
(Windows)或java
(Java虚拟机)。
-
os.getcwd()
:- 获取当前工作目录的路径。
- 返回一个字符串表示当前工作目录的路径。
-
os.chdir(path)
:- 修改当前工作目录为指定的路径。
path
参数为目标路径的字符串。
-
os.listdir(path='.')
:- 返回指定目录中所有文件和目录的名称列表。
- 默认参数为当前工作目录,返回该目录下的所有文件和目录的名称。
-
os.mkdir(path, mode=0o777)
:- 创建一个新的目录。
path
参数为要创建的目录路径的字符串。mode
参数为目录的权限模式,默认权限为0o777
。
-
os.makedirs(name, mode=0o777, exist_ok=False)
:- 递归地创建多层目录。
name
参数为要创建的目录路径的字符串。mode
参数为目录的权限模式,默认权限为0o777
。exist_ok
参数为一个布尔值,表示如果目录已存在是否引发异常。
-
os.rmdir(path)
:- 删除指定的空目录。
path
参数为要删除的目录路径的字符串。
-
os.remove(path)
:- 删除指定的文件。
path
参数为要删除的文件路径的字符串。
-
os.rename(src, dst)
:- 将文件或目录重命名。
src
参数为原始的文件或目录名称。dst
参数为新的文件或目录名称。
-
os.path.join(path, *paths)
:- 将多个路径组合成一个完整的路径。
path
参数为第一个路径字符串。*paths
为可选参数,表示附加的路径。
-
os.path.isfile(path)
:- 判断指定路径是否为文件。
path
参数为要检查的路径字符串。
-
os.path.isdir(path)
:- 判断指定路径是否为目录。
path
参数为要检查的路径字符串。
-
os.path.exists(path)
:- 判断指定的路径是否存在。
path
参数为要检查的路径字符串。
-
os.path.basename(path)
:- 返回路径中的文件名部分。
path
参数为要获取文件名的路径字符串。
-
os.path.dirname(path)
:- 返回路径中的目录部分。
path
参数为要获取目录的路径字符串。
-
os.path.abspath(path)
:- 返回指定路径的绝对路径。
path
参数为要获取绝对路径的路径字符串。
-
os.path.getsize(path)
:- 返回指定文件的大小,单位为字节。
path
参数为要获取大小的文件路径字符串。
-
os.path.split(path)
:- 将路径拆分为目录和文件名两部分。
path
参数为要拆分的路径字符串。- 返回一个包含目录和文件名的元组。
这些函数可以帮助你在Python中操作文件和目录,获取文件的属性,或者通过路径进行操作。具体使用哪些函数,取决于你的需求和场景。
线程和进程的定义的区别
**线程(Thread)**是操作系统能够进行运算调度的最小单位。一个进程可以拥有多个线程,它们共享进程所拥有的资源,如内存空间、文件句柄等。线程是进程内的一个执行单元,多个线程之间可以并发执行,共享进程的上下文环境。线程之间的切换开销较小,可以提高程序的并发性和响应速度。
**进程(Process)**是操作系统中的一个独立的执行单元,拥有独立的地址空间和资源。每个进程有自己的代码段、数据段、堆栈段和文件等资源。进程是操作系统分配资源的最小单位,多个进程之间相互独立,彼此之间不共享资源,通信需要通过进程间的通信方式。
区别如下:
- 调度:进程由操作系统进行调度,而线程由进程进行调度。操作系统可以同时调度多个进程,每个进程可能有多个线程,线程之间共享进程的资源。
- 资源占用:进程有独立的内存空间和资源,而线程共享进程的资源,包括内存空间、文件和网络连接等。
- 创建和销毁开销:创建进程的开销较大,涉及资源的分配和初始化;而创建线程的开销较小,可以复用进程的资源。
- 通信方式:进程间通信需要使用进程间通信机制,如管道、共享内存、消息队列等;而线程之间直接共享进程的数据,可以通过共享内存、全局变量等方式进行通信。
- 安全性:由于线程共享进程的资源,线程之间的操作需要进行同步和互斥控制,否则可能引发数据竞争和死锁等问题。而进程之间相互独立,不会受到其他进程的影响。
进程间的通讯Queue
基础
import multiprocessing
queue = multiprocessing.Queue(2) # 容量为2的队列
queue.put("message1")
queue.put("message2")
print(queue.full()) # 用于检查队列是否已满
print(queue.qsize()) # 返回数据条数
print(queue.get()) # message1
print(queue.get()) # message2
print(queue.empty()) # True 判断队列是否为空
queue.get() # 阻塞等待
queue.get_nowait() # 不会阻塞等待,若无消息在队列中直接error
一个进程间用队列通讯的例子
import random
import time
import multiprocessing
def put_data(queue):
for item in 'ABC':
queue.put(item)
time.sleep(random.random())
def get_data(queue):
while True:
if not queue.empty():
print(queue.get())
time.sleep(random.random())
else:
break
if __name__ == '__main__':
queue = multiprocessing.Queue()
process1 = multiprocessing.Process(target=put_data, args=(queue,))
process2 = multiprocessing.Process(target=get_data, args=(queue,))
process1.start()
process1.join()
process2.start()
process1.join()
print('主程序退出')
result:
A
B
C
主程序退出
进程池
import random
import time
import multiprocessing
def work(message):
start = time.time()
time.sleep(random.random())
stop = time.time()
print(f"{message}程序执行时间{stop - start}")
if __name__ == "__main__":
pool = multiprocessing.Pool(3)
for i in range(10):
pool.apply_async(work, (i,))
"""
apply_async是multiprocessing.Pool类的一个方法,用于将函数异步地提交给进程池进行执行。
apply_async的语法为:
apply_async(func, args=(), kwds={}, callback=None)
参数说明:
func:需要在进程中执行的函数。
args:函数的位置参数(元组)。
kwds:函数的关键字参数(字典)。
callback:可选参数,指定一个回调函数,在子进程执行完毕后调用该回调函数。
"""
pool.close() # 关闭进程池,不再接受新的任务。
pool.join() # 阻塞主进程,等待所有任务完成。
result:
0程序执行时间0.26610374450683594
2程序执行时间0.5086164474487305
1程序执行时间0.6840624809265137
3程序执行时间0.666872501373291
6程序执行时间0.4043400287628174
4程序执行时间0.90093994140625
7程序执行时间0.1246500015258789
5程序执行时间0.8555760383605957
9程序执行时间0.2657303810119629
8程序执行时间0.7948851585388184
协程
协程(Coroutine)是一种用户空间的轻量级线程,也被称为用户态线程或纤程。协程允许在单个线程内通过切换来实现多个任务的并发执行,从而提高程序的效率和资源利用率。
原理
协程的原理是基于一种特殊的执行模型,其中协程函数是通过迭代器(iterator)的方式进行执行的。
当一个协程函数被调用时,它会返回一个迭代器对象。这个迭代器对象可以通过__next__()
方法进行迭代,每次调用__next__()
方法会执行协程函数中的一部分代码,直到遇到yield
关键字。
yield
关键字在协程中有两个作用:
- 将协程函数分成多个部分,在每次调用
__next__()
方法时执行一个部分。 - 可以通过
yield
将结果返回给调用方,然后在下一次调用__next__()
方法时恢复执行。
通过这种方式,协程函数可以在执行过程中暂停、恢复和返回值,实现了协程的异步执行和协作式调度。
协程的关键特性是它的执行是由用户自己控制的。调用方可以主动让出执行权,以便其他协程能够执行。这种切换执行权的操作在协程中通常称为"yield",使得执行权从一个协程切换到另一个协程。
协程可以在单个线程中实现并发执行,因为它们是按照用户的要求逐个交替执行的。与线程相比,协程的切换开销较低,因为不需要切换内核上下文。协程之间可以共享数据,但需要用户自己实现同步和互斥。此外,协程还可以通过异步操作进行非阻塞地等待和执行。
步骤
- 创建事件循环对象:首先,创建一个事件循环对象,用于管理协程任务的调度和执行。
- 注册协程任务:通过
async
关键字定义协程函数,并将其封装成任务对象。然后,将任务对象注册到事件循环中。 - 事件循环调度任务:事件循环按照一定的策略从注册的任务中选择一个任务,将其加入到待执行队列中。
- 执行任务:事件循环从待执行队列中取出一个任务,执行其协程函数中的代码。如果协程函数中遇到
await
关键字,任务会暂停执行,并返回到事件循环。 - 任务切换:当协程函数暂停执行时,事件循环会从待执行队列中选择下一个任务继续执行。通过任务切换机制,事件循环可以实现协程任务的切换和并发执行。
- 循环执行:循环执行步骤4和步骤5,直到所有任务都完成。
特点
与传统的线程或进程相比,协程的主要特点是:
- 协程是由用户自己控制的,可以根据需要主动让出执行权。
- 协程的切换不需要内核介入,切换时的开销较低。
- 协程之间可以共享数据,但需要用户自己实现同步。
- 协程适用于任务数量多、计算密集型的场景。
最原始的协程
import time
def func_a():
while True:
print('生成器任务...')
yield
time.sleep(0.5)
def func_b(obj):
while True:
print('普通任务...')
next(obj)
a = func_a()
print(a)
func_b(a)
result:
普通任务...
生成器任务...
普通任务...
生成器任务...
普通任务...
生成器任务...
普通任务...
生成器任务...
普通任务...
...
事件循环伪代码
# 定义一个事件循环
class EventLoop:
def __init__(self):
self.tasks = [] # 任务列表
def add_task(self, task):
self.tasks.append(task) # 加入任务列表
def run(self):
while self.tasks:
task = self.tasks.pop(0) # 取出第一个任务
try:
result = task() # 执行任务
if result is not None:
self.add_task(result) # 将结果作为新的任务加入任务列表
except StopIteration:
pass
# 定义一个协程任务
def coroutine_task():
yield from sub_task() # 使用 yield from 来暂停任务的执行,并将控制权交给 sub_task()
# 定义一个子任务
def sub_task():
print("Sub Task")
yield
# 创建事件循环对象
event_loop = EventLoop()
# 将协程任务加入事件循环
event_loop.add_task(coroutine_task)
# 运行事件循环
event_loop.run()
asyncio
asyncio
的原理是基于事件循环和协程。事件循环负责调度和执行协程任务。当事件循环运行时,它会不断地轮询已注册的协程任务,检查任务是否已经完成或者是否被阻塞。如果任务完成,事件循环会唤醒它并执行下一步操作。如果任务被阻塞(例如等待IO操作的完成),事件循环会将控制权转移到其他没有被阻塞的任务上,以便并发执行。
asynico中一些常见的函数
下面是asyncio
中常用的函数及其参数的实例详解:
-
asyncio.run(main, *, debug=False)
main
: 要运行的协程函数或任务。debug
(可选): 控制是否启用调试模式,默认为False。
import asyncio async def main(): print("Hello, asyncio!") asyncio.run(main())
这个函数用来运行主协程或任务,并自动创建和关闭事件循环。在上面的例子中,
main()
是要运行的协程函数,asyncio.run()
会创建一个事件循环并运行main()
,直到协程完成。 -
asyncio.get_event_loop()
import asyncio def callback(n): print(f"Callback executed with {n}") async def main(): print("Creating event loop...") loop = asyncio.get_event_loop() print("Scheduling callback...") loop.call_later(2, callback, 42) await asyncio.sleep(3) print("Finished") asyncio.run(main())
这个函数用于获取当前执行上下文中的事件循环对象。在上面的例子中,我们先创建了一个事件循环,然后使用
call_later()
方法调度了一个在2秒后执行的回调函数。 -
asyncio.ensure_future(coro_or_fut)
import asyncio async def my_coroutine(): await asyncio.sleep(1) return "Coroutine completed" async def main(): task = asyncio.ensure_future(my_coroutine()) await asyncio.sleep(2) print(task.result()) asyncio.run(main())
这个函数将协程对象或可等待对象包装成任务对象,并添加到事件循环中。在上面的例子中,
my_coroutine()
是一个协程函数,使用asyncio.ensure_future()
包装成任务对象task
,然后我们等待2秒,取出任务的结果并打印。 -
asyncio.gather(*coros_or_futs, loop=None, return_exceptions=False)
*coros_or_futs
: 要并行运行的协程对象或任务对象。loop
(可选): 要使用的事件循环。return_exceptions
(可选): 控制是否返回异常对象,默认为False。
import asyncio async def coroutine_1(): await asyncio.sleep(2) return "Coroutine 1" async def coroutine_2(): await asyncio.sleep(1) raise ValueError("Coroutine 2 error") async def main(): results = await asyncio.gather(coroutine_1(), coroutine_2(), return_exceptions=True) # 等待所有任务执行完成一起返回返回值 print(results) asyncio.run(main())
这个函数用来并行运行多个协程或任务,并等待它们全部完成。在上面的例子中,
coroutine_1()
和coroutine_2()
是两个协程函数,使用asyncio.gather()
将它们并行运行。return_exceptions=True
表示即使其中一个协程出现异常,也会返回对应的异常对象。 -
asyncio.wait(aws, *, loop=None, timeout=None, return_when=asyncio.ALL_COMPLETED)
aws
: 一个可迭代的协程对象或任务对象。loop
(可选): 要使用的事件循环。timeout
(可选): 超时时间。return_when
(可选): 控制返回条件,默认为asyncio.ALL_COMPLETED
。
import asyncio async def coroutine_1(): await asyncio.sleep(2) return "Coroutine 1" async def coroutine_2(): await asyncio.sleep(1) return "Coroutine 2" async def main(): tasks = [coroutine_1(), coroutine_2()] done, pending = await asyncio.wait(tasks, timeout=3) #pending(元组)未完成了和正在执行的任务 for task in done: print(task.result()) asyncio.run(main())
这个函数用来并行运行多个协程或任务,并等待它们中的至少一个完成。在上面的例子中,
coroutine_1()
和coroutine_2()
是两个协程函数,使用asyncio.wait()
将它们并行运行,并设置了3秒的超时时间。完成的任务存储在done
集合中。 -
asyncio.sleep(delay, result=None, *, loop=None)
delay
: 延迟的秒数,可以是整数或浮点数。result
(可选): 可以作为此协程的返回值,默认为None。loop
(可选): 要使用的事件循环。
import asyncio async def main(): print("Before sleep") await asyncio.sleep(1) print("After sleep") asyncio.run(main())
此函数用于延迟指定的时间,使协程暂停执行。在上面的例子中,我们使用
asyncio.sleep()
将执行暂停1秒。 -
asyncio.create_task(coro, *, name=None)
coro
: 要封装成任务的协程对象。name
(可选): 任务的名称。
import asyncio async def my_coroutine(): await asyncio.sleep(1) print("Coroutine executed") async def main(): task = asyncio.create_task(my_coroutine()) await task asyncio.run(main())
此函数用于将协程对象封装成一个任务,以便可以在事件循环中并发运行。在上面的例子中,我们使用
asyncio.create_task()
创建了一个任务,然后等待任务完成。 -
asyncio.wait_for(aw, timeout, *, loop=None)
aw
: 要等待的可等待对象,通常是协程或任务。timeout
: 等待的最长时间,可以是整数或浮点数。loop
(可选): 要使用的事件循环。
import asyncio async def my_coroutine(): await asyncio.sleep(2) return "Coroutine completed" async def main(): try: result = await asyncio.wait_for(my_coroutine(), timeout=1) print(result) except asyncio.TimeoutError: print("Timeout!") asyncio.run(main())
此函数用于等待一个可等待对象,直到它完成或达到超时时间。在上面的例子中,我们使用
asyncio.wait_for()
等待my_coroutine()
执行,并设置超时时间为1秒。如果超时,则会引发asyncio.TimeoutError
异常。
python3.6之后
import asyncio
async def func(name, address):
print('other任务正在被执行') # 代码预处理,遇到await停止
await asyncio.sleep(1)
print('other任务执行完毕')
return f'{name},{address}'
async def main(loop=None):
task_1 = asyncio.create_task(func('wzx', 'shantou'))
task_2 = asyncio.create_task(func('csy', 'shantou'))
result1 = await task_1 # 给loop提交协程对象(或者task future),阻塞等待返回值
result2 = await task_2
print(result1, result2)
asyncio.run(main())
python3.6以及之前
import asyncio
async def func(name, address):
print('other任务正在被执行')
await asyncio.sleep(1)
print('other任务执行完毕')
return f'{name},{address}'
async def main(loop=None):
task_1 = loop.create_task(func('wzx', 'shantou'))
task_2 = loop.create_task(func('csy', 'shantou'))
result1 = await task_1
result2 = await task_2
print(result1, result2)
loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
result:
other任务正在被执行
other任务正在被执行
other任务执行完毕
other任务执行完毕
wzx,shantou csy,shantou
多个协程任务
import asyncio
# 定义多个协程任务
async def my_coroutine1():
await asyncio.sleep(1)
return "Coroutine 1"
async def my_coroutine2():
await asyncio.sleep(2)
return "Coroutine 2"
async def main():
# 使用ensure_future将多个协程对象包装成任务对象并存储在列表中
tasks = [
asyncio.ensure_future(my_coroutine1()),
asyncio.ensure_future(my_coroutine2())
]
# 将任务列表加入事件循环进行调度和执行
done, pending = await asyncio.wait(tasks)
for item in done:
print(item.result())
asyncio.run(main())
result:
Coroutine 1
Coroutine 2
import asyncio
import requests
async def fetch_data(url):
# 模拟网络请求操作
await asyncio.sleep(1)
response = requests.get(url=url).text
return "Data from {}".format(response)
async def main():
urls = ["https://www.baidu.com/", "https://www.sogou.com/"]
tasks = [fetch_data(url) for url in urls]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
异步迭代器
# 异步迭代器
import asyncio
class MyAsyncIter:
def __init__(self, count):
self.count = count
async def iter_num(self):
await asyncio.sleep(0.5)
self.count -= 1
if self.count == 0:
return None
return self.count
def __aiter__(self):
return self
async def __anext__(self):
value = await self.iter_num()
if value is None:
raise StopAsyncIteration
return value
async def process_number(number):
print(f"Processing number: {number}")
await asyncio.sleep(1)
print(f"Done processing number: {number}")
async def main():
async for number in MyAsyncIter(5):
asyncio.create_task(process_number(number))
asyncio.run(main())
result:
Processing number: 4
Processing number: 3
Done processing number: 4
Processing number: 2
Done processing number: 3
Processing number: 1
Done processing number: 2
回调函数
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, World!'
@app.route('/callback-example', methods=['POST'])
def callback_example():
data = request.json
process_data(data, callback_function)
return 'Callback example endpoint'
def process_data(data, callback):
# 处理数据的逻辑
# ...
# 处理完数据后调用回调函数
callback(data)
def callback_function(data):
# 处理回调数据的逻辑
print(f"Callback function received data: {data}")
if __name__ == '__main__':
app.run()
在这个示例中,我们使用Flask创建了一个简单的Web应用。当访问根路径'/'
时,会返回一个简单的"Hello, World!"消息。
我们还定义了一个'/callback-example'
的路由,用于模拟处理回调函数的例子。当收到POST请求时,我们调用process_data
函数处理数据,并传递一个回调函数callback_function
。
process_data
函数负责处理数据的逻辑,然后调用回调函数来处理处理后的数据。在本例中,我们简单地打印出回调函数接收到的数据。
最后,我们启动Flask应用。当访问'/callback-example'
端点且发送POST请求时,会触发数据处理和回调函数的调用。
当你运行这个示例应用并发送POST请求到'/callback-example'
时,你会看到回调函数被调用并打印出数据。这展示了回调函数在Web开发中处理事件回调的应用。
异步上下文管理器
# 异步上下文管理器
import asyncio
class AsyncContextManager:
def __init__(self, conn=None):
self.conn = conn
async def get_data(self):
return '模拟数据库增删查改...'
async def __aenter__(self):
# 进入上下文时执行的逻辑
self.conn = await asyncio.sleep(1, result='连接成功...')
print(self.conn)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
# 退出上下文时执行的逻辑
print('关闭数据库...')
async def async_example():
async with AsyncContextManager() as fp:
# 在上下文中执行异步操作
result = await fp.get_data()
print(result)
async def main():
await async_example()
if __name__ == '__main__':
asyncio.run(main())
result:
连接成功...
模拟数据库增删查改...
关闭数据库...
python创建虚拟环境
在 Visual Studio Code 中创建和使用 Python 虚拟环境可以帮助你隔离不同项目的依赖和包,方便开发和管理。以下是在 Visual Studio Code 中创建和激活 Python 虚拟环境的步骤:
-
打开 Visual Studio Code,并在打开的项目文件夹中打开终端。
-
在终端中输入以下命令创建虚拟环境,可以指定环境的名称和位置:
python -m venv <env_name>
例如:
python -m venv myenv
这将在当前项目文件夹中创建一个名为
myenv
的虚拟环境。 -
虚拟环境创建完成后,需要激活虚拟环境。在终端中输入以下命令来激活虚拟环境:
-
Windows:
.\myenv\Scripts\activate
-
macOS/Linux:
source myenv/bin/activate
你会注意到终端提示符前面会出现虚拟环境的名称,表示虚拟环境已激活。
-
-
现在你可以在激活的虚拟环境中安装包或执行 Python 脚本了。
注意:激活虚拟环境后,使用 python
命令将使用虚拟环境中的 Python 解释器。如果要停止使用虚拟环境,可以在终端中使用 deactivate
命令。
使用CTRL+shift+p
命令: 打开命令交互面板, 在命令面板中可以输入命令进行搜索(中英文都可以),然后执行。命名面板中可以执行各种命令,包括编辑器自带的功能和插件提供的功能
在打开的命令面板中输入下述命令:
Python: Select Interpreter
选择想要的虚拟环境