14. 迭代器与生成器

  1. 可迭代的对象:使用iter内置函数可以获取迭代器的对象。如果对象实现了__iter__方法,能够返回一个迭代器,那么对象就是可迭代的。

  2. 迭代器:对象实现了__next__返回序列中的下一个元素。迭代器还实现了__iter__返回self,因此迭代器也可以迭代。

  3. 生成器函数:函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。

  4. 关系:所有的生成器都是迭代器,因为生成器完全实现了迭代器接口。生成器只是另一种实现可迭代对象的方式而已。除了会创建和保存程序状态,当生成器终结时,还会自动抛出StopIteration异常。

15. 上下文管理器和with块

  1. else可以用在for, while, try的后面,循环体正常执行完毕(没有break, 没有except)后退出时执行else的内容。

  2. 上下文管理器对象存在的目的是管理with块,简化try/finally模式。

  3. ContextManger实现的两种方式:

    1. 编写一个类,实现__enter____exit__方法
    2. 调用@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. 协程

  1. b = yield a 表达式左右两边在两次send()执行。

  2. 调用方调用next(cor)之后,协程执行到yield a并暂停,调用方收到返回值;调用方调用cor.send(b),从中断点继续执行,将接收到的b赋值给等号左边的b,继续向下执行。当碰到下一个yield a时再次暂停并产出a。

  3. yield from实现类似于管道的功能,调用方-委派生成器-子生成器。

  4. 生成器函数需要初始化也就是f=func()后f才是generator,func是function类型。

3. 协程实现生产者消费者模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import time

def producer(consumer):
ret = next(consumer)
for i in range(10):
print('[PRODUCER] produce: %d' % i)
ret = consumer.send(i)
print('[PRODUCER] consumer return: %s' % ret)
consumer.close()


def consumer():
ret = ''
while True:
i = yield ret
print('[CONSUMER] consume: %d' % i)
time.sleep(1)
ret = 'OK'

c = consumer()
producer(c)