Fluent Python(14-16)——从迭代器到协程
14. 迭代器与生成器
可迭代的对象:使用iter内置函数可以获取迭代器的对象。如果对象实现了
__iter__
方法,能够返回一个迭代器,那么对象就是可迭代的。迭代器:对象实现了
__next__
返回序列中的下一个元素。迭代器还实现了__iter__
返回self,因此迭代器也可以迭代。生成器函数:函数的定义体中有
yield
关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。关系:所有的生成器都是迭代器,因为生成器完全实现了迭代器接口。生成器只是另一种实现可迭代对象的方式而已。除了会创建和保存程序状态,当生成器终结时,还会自动抛出StopIteration异常。
15. 上下文管理器和with块
else可以用在for, while, try的后面,循环体正常执行完毕(没有break, 没有except)后退出时执行else的内容。
上下文管理器对象存在的目的是管理with块,简化try/finally模式。
ContextManger实现的两种方式:
- 编写一个类,实现
__enter__
和__exit__
方法 - 调用
@contextlib.contextmanager
装饰器,将生成器函数转变为上下文管理器,函数中yield之前的语句对应__enter__
内容,yield xxx为返回的对象(对应到with func() as xxx的xxx),yield之后的语句对应__exit__
内容。始终用try语句包裹yield语句
- 编写一个类,实现
16. 协程
1. 进程、线程、协程
进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
协程: 可以理解一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
协程与子程序的区别:调用子程序执行到return之后子程序就完全返回到主程序了,而协程会保存现场,下次从中断点继续执行。(中断)
协程与线程优势:1. 没有多线程切换的开销,寄存器和栈都保存在用户态。2. 不需要锁机制,因为是在一个线程中,不存在写变量冲突问题。所以协程是一种协作式多任务,多线程是一种抢占式多任务。
Python里CPU密集型使用多进程,IO密集型使用多线程或协程。
2. 协程
b = yield a
表达式左右两边在两次send()执行。调用方调用
next(cor)
之后,协程执行到yield a
并暂停,调用方收到返回值;调用方调用cor.send(b)
,从中断点继续执行,将接收到的b赋值给等号左边的b,继续向下执行。当碰到下一个yield a
时再次暂停并产出a。yield from
实现类似于管道的功能,调用方-委派生成器-子生成器。生成器函数需要初始化也就是
f=func()
后f才是generator,func是function类型。
3. 协程实现生产者消费者模型
1 | import time |