活动地址:CSDN21天学习挑战赛
学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…
一、生成器——特殊的迭代器
1.创建生成器的方式
a.推导式的方式
# 生成器是一个特殊的迭代器
num = [x for x in range(10)]
print(num) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
num = (x for x in range(10)) # 生成了一个对象---->生成器
print(num) # <generator object <genexpr> at 0x000001D532D85B60>
for i in num:
print(i)
"""
0
1
2
3
4
5
6
7
8
9
"""
# 以上两个方式一个返回方式占用空间(推导式),一个返回方式是生成值的方法节省空间(生成器)
以上使用推导式创建的返回值num是一个列表变量储存了所有的值,而以生成器的方式即把列表变成元组最后返回给变量num的是一个这个函数的引用当for循环遍历的时候循环一次就往这个函数的里取一次,直至全部取完。
b.yield函数的使用
def create_num(all_num):
a, b = 0, 1
for i in range(all_num):
# print(a)
yield a # 函数里面只要有yield就不是函数了而是一个生成器模板
# 当程序运行到yield a时程序会暂停在这把a的值返回给num,然后打印num
# 接下来继续执行下面的代码a, b = b, a + b
a, b = b, a + b
# 如果在调用create_num()的时候,
# 发现这个函数里面有yield那么此时不是调用函数而是创建一个生成器对象
obj = create_num(10) # 生成器
# iter() # 可迭代对象放进去生成迭代器
# next() # 调用迭代器里面的__next__开始取值
ret = next(obj) # yield的值第一个为0故直接这样打印0
print(ret) # 0ret = next(obj) # yield的值第一个为0故直接这样打印0
ret = next(obj) # yield的值第二个为1故直接这样打印1
print(ret) # 1
for num in obj:
print(num)
# 除了第一个是从开头执行的,其他的都是从暂停的地方开始执行
讲解:
1.yield a函数里面只要有yield就不是函数了而是一个生成器模板(创建一个生成器对象)
2.当程序运行到yield a时程序会暂停在这把a的值返回给num,然后打印num
3.接下来继续执行下面的代码a, b = b, a + b
def create_num(all_num):
a, b = 0, 1
for i in range(all_num):
print(f'对象里面直接输出{a}')
yield a
a, b = b, a + b
obj = create_num(10) # 创建生成器
# iter() # 可迭代对象放进去生成迭代器
# next() # 调用迭代器里面的__next__开始取值
ret = next(obj)
print(f'ret取值得到{ret}') # ret = next(obj)
ret = next(obj)
print(f'ret取值得到{ret}')
最后版本:
def create_num(all_num):
a, b = 0, 1
for i in range(all_num):
# print(a)
yield a # 函数里面只要有yield就不是函数了而是一个生成器模板
# 当程序运行到yield a时程序会暂停在这把a的值返回给num,然后打印num
# 接下来继续执行下面的代码a, b = b, a + b
a, b = b, a + b
obj = create_num(10) # 生成器
for num in obj:
print(num)
# 除了第一个是从开头执行的,其他的都是从暂停的地方开始执行
在for遍历的时候就和上面分析的一样(使用next()进行取值取一次打印一次然后直到全部取完)
研究:
def create_num(all_num):
a, b = 0, 1
for i in range(all_num):
# print(a)
yield a
a, b = b, a + b
obj = create_num(10)
obj2 = create_num(2)
ret = next(obj)
print("obj:", ret)
ret = next(obj)
print("obj:", ret)
ret = next(obj2)
print("obj2:", ret)
ret = next(obj)
print("obj:", ret)
# 结论:两个互不干扰
for num in obj:
print(num)
2.通过异常判断生成器的结束
def create_num(all_num):
a, b = 0, 1
for i in range(all_num):
# print(a)
yield a
a, b = b, a + b
return "---ok取完了---" # 函数返回值不会影响
obj2 = create_num(2)
while True:
try:
ret = next(obj2)
print("obj2:", ret) # 不会受return影响
except Exception as ret:
print(ret)
print(ret.value) # 打印return方法加此句代码
break
# 生成器是一个特定的迭代器
3.使用send()方式来启动生成器
def create_num(all_num):
a, b = 0, 1
for i in range(all_num):
ret = yield a # 右边实现把a的值传出去,左边是赋值语句你send(“hh”),那么yield a 就是hh
print(">>>ret>>>", ret)
a, b = b, a + b
obj2 = create_num(2)
ret = next(obj2)
print(ret)
ret = obj2.send(None) # send不要放在开头放在开头没有数字来接收send()里面的数据第一次可以send(None)
print(ret)
"""
可以用next()和send()方式区别在于send()方式可以传递参数
"""
1.可以用next()和send()方式区别在于send()方式可以传递参数
2.send不要放在开头放在开头没有数字来接收send()里面的数据
二、对生成器总结
迭代器保存的是生成的代码而不是结果
生成器:让函数暂停执行并返回一定数据,还有就是继续执行时对接暂停执行
生成器是特殊的迭代器
三、使用协程完成多任务
1.使用yield完成
import time
def task_1():
while True:
print("----work1---")
time.sleep(0.1)
yield
def task_2():
while True:
print("----work2---")
time.sleep(0.1)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
# 并发
# 进程要创建资源啥的比线程浪费,而协程比线程更好
运行结果:
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
...省略...
进程要创建资源啥的比线程浪费,而协程比线程更好
2.使用greenlet完成多任务
from greenlet import greenlet
import time
def test1():
while True:
print("---A---")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("---B---")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
# 切换到gr1中运行
gr1.switch()
# 这个对yield进行了封装
原理没变只是通过greenlet对其进行了封装
3.使用gevent完成多任务
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
# 必须全部重构手动把time.sleep()(耗时操作)转变为gevent.sleep(0.5)
gevent.sleep(0.5) # 遇到延时操作自动切换,协程利用了原来等待耗时操作的时间去做其他的事情
g1 = gevent.spawn(f, 5) # 拿greenlet进行封装
g2 = gevent.spawn(f, 5) # (执行哪个函数, 传入函数的参数)
g3 = gevent.spawn(f, 5) # 创建了对象并不会马上执行
g1.join() # 等待g1完成,耗时自动切换进去
g2.join()
g3.join() # 协程依赖于线程
# <Greenlet at 0x238c9a967a0: f(5)> 0
# <Greenlet at 0x238c9a96b00: f(5)> 0
# <Greenlet at 0x238c9a972e0: f(5)> 0
# <Greenlet at 0x238c9a967a0: f(5)> 1
# <Greenlet at 0x238c9a96b00: f(5)> 1
# <Greenlet at 0x238c9a972e0: f(5)> 1
# <Greenlet at 0x238c9a967a0: f(5)> 2
# <Greenlet at 0x238c9a96b00: f(5)> 2
# <Greenlet at 0x238c9a972e0: f(5)> 2
# <Greenlet at 0x238c9a967a0: f(5)> 3
# <Greenlet at 0x238c9a96b00: f(5)> 3
# <Greenlet at 0x238c9a972e0: f(5)> 3
# <Greenlet at 0x238c9a967a0: f(5)> 4
# <Greenlet at 0x238c9a96b00: f(5)> 4
# <Greenlet at 0x238c9a972e0: f(5)> 4
讲解:
可以看到遇到耗时操作如sleep(0.5)程序不停留在那等而是先跳过(利用这部分时间)
使用总结:
import gevent
g1 = gevent.spawn(f, 5) # 创建实例对象 gevent.spawn(函数名,传入的参数)
g1.join() # 开始执行
4.gevent打补丁
import gevent
import time
from gevent import monkey
monkey.patch_all() # 将所有的耗时操作都自动检测并换为gevent里面的模块
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
# 必须全部重构手动把time.sleep()(耗时操作)转变为gevent.sleep(0.5)
# 那如何不手动改这些耗时操作的代码让他自己自动改呢
time.sleep(0.5) # 用的是原来的time但是monkey.patch_all()会把耗时操作全部改
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
# g1.join()
# g2.join()
# g3.join()
gevent.joinall([g1, g2, g3]) # 与上式注释的效益一样
对于耗时的函数调用需要使用gevent所规定的(如time.sleep(0.5)),这样太烦了
from gevent import monkey
monkey.patch_all()以上代码自动化检测耗时代码并换成gevent的模板
四、应用
1.图片下载器
import urllib.request
import gevent
count = 0
def down_load(url, name):
req = urllib.request.urlopen(url)
img_content = req.read()
with open(name, "wb") as file:
file.write(img_content)
def main():
gevent.joinall([
gevent.spawn(down_load, "https://img1.baidu.com/it/u=3079431989,3499240801&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1657299600&t=2a1f03f85b3c4fd52005ab1e0d3cf6d4", "2.jpg"),
gevent.spawn(down_load,"https://img0.baidu.com/it/u=2810806728,4166654119&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500", "3.jpg")
])
if __name__ == "__main__":
main()
# 网络下载是一个耗时过程
五、对多任务的总结
进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很最大,效率很低
线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
协程切换任务资源很小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中所以是并发
六、预告
明天更新正则表达式,最近因练车所以有点耽误了。
我们现在是被前辈们的光所照耀,未来将会是我们照耀后辈。
一步一步来,加油。