最近在用tornado+peewee_async+aio系列写异步的web后台,之前也深入的学习了Python的异步写法,曾经还写了一个库aiossdb,但是时间太长不用又有点遗忘了,于是这里再复习一遍,然后记下笔记,以后如果再忘还可以翻一下笔记🤣

所有生成器都是迭代器,因为生成器完全实现了迭代器的接口。迭代器用于从集合中取出元素,而生成器用于凭空生成元素。

在Python3中,内置的range()返回一个类似生成器的对象,而以前是返回完整的列表。

解释器需要迭代对象x时候,会自动调用iter(x)

  • 检查对象是否实现了__iter__()方法,如果实现了就去调用它,获取一个迭代器
  • 否则,如果实现__getitem__方法,Python会创建一个迭代器,尝试按顺序获取元素
  • 否则,Python抛出TypeError异常,指明该对象不可迭代

所以Python可以从可迭代的对象中获取迭代器

1
2
3
4
5
6
7
8
s = 'abc'
it = iter(s) # 创建迭代器
while True:
try:
print(next(it))
except StopIteration:
del it
break

标准迭代器接口有两个方法:

  • __next__
  • __iter__

所以迭代器是这样的对象:

实现了无参数的__next__方法,返回序列中的下一个元素,如果没有元素了,抛出StopIteration异常,Python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代,iter()函数会对序列做特殊处理,所以也可以迭代。

可迭代对象有一个__iter__方法,每次都实例化了一个新的迭代器。而迭代器要实现__next__方法,返回单个元素,另外要实现__iter__方法,返回迭代器本身。

所以迭代器可以迭代,但是可迭代的对象不是迭代器,关键在于__next__方法。

可迭代对象一定不能是自身的迭代器,也就是说,可迭代对象必须实现__iter__方法,但是不能实现__next__方法。因为有可能要从同一个可迭代的实例中获取多个独立的迭代器,每个迭代器都能维护自身的内部状态,所以需要实现一个新的独立的迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self

使用生成器函数来替代iterator

1
2
3
4
class Sentence:
def __iter__(self):
for word in self.words:
yield word

在这里迭代器是生成器对象,每次调用__iter__方法都会自动创建,所以这里的__iter__方法是生成器函数,调用时会创建一个实现了迭代器接口的生成器对象,定义体含有yield关键字,所以生成器函数是生成器工厂。调用生成器函数返回生成器,生成器产生或者生成值。生成器中的return语句会出发生成器对象抛出StopIteration异常。

在Python3.3之后,return后面的值可以使得调用方从异常对象中获取返回值,但是只有将生成器函数作为协程使用的时候,才有意义。

列表推导是制造列表的工厂,生成器表达式就是制造生成器的工厂。

1
2
3
class Sentence:
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))

__iter__方法会得到一个生成器对象

itertools.count 函数用于生成无穷等差数列

把self.begin与self.step和index的乘积相加,而不是累加,这样可以降低处理浮点数时候累积效应致错的风险。

1
2
3
4
5
6
7
8
def aritprog_gen(begin, step, end=None):
result = type(begin + step)(begin)
forever = end is None
index = 0
while forever or result < end:
yield result
index += 1
result = begin + step * index

插:

当我们想所有环境下都统一显示的话,可以重构__repr__方法;当我们想在不同环境下支持不同的显示,例如终端用户显示使用__str__,而程序员在开发期间则使用底层的__repr__来显示,实际上__str__只是覆盖了__repr__以得到更友好的用户显示。

也就是说只写__str__方法可能在终端环境下就不能良好显示,只有print可以表现出来。

yield from

1
2
3
4
def chain(*iterables):
for i in iterables:
yield from i
list(chain(s, t))

这个其实就是一个生成器,yield from代替了内存循环,本来是这个样子

1
2
3
4
5
def chain(*iterables):
for it in iterables:
for i in it:
yield i
list(chain(s, t))

yield from 除了代替循环之外,还可以创建通道,把内层生成器直接与外层生成器的客户端联系起来,把生成器当成协程使用的时候,这个通道比较重要,不仅能为客户端代码生成值,也能使用客户端代码提供的值。

逐行读取文件,直到遇到空行或者到达文件末尾为止:

1
2
3
with open('mydata.txt') as fp:
for line in iter(fp.readline, '\n'):
process_line(line)

把生成器当成协程

.send()方法使得生成器前进到下一个yield语句,还允许使用生成器的客户把数据发给自己,发送的参数都会成为生成器函数定义体中国年对应的yield表达式的值。__next__()方法只允许客户从生成器中获取数据

  • 生成器是用于生成供迭代的数据
  • 协程是数据消费者
  • 协程与迭代无关,虽然会用yield产出值

协程

补充了上面的基础知识,开始协程的深入研究。。。

yield item会产出一个值,提供给next的调用方,同时作出让步,暂停执行生成器,让调用方继续工作,直到需要使用另一个值的时候再调用next

在协程中一般yield在表达式右边,datum = yield,这样产出是None,协程会从调用房接收数据,调用方把数据提供给协程使用的是.send(datum)方法

yield是一种流程控制工具,使用它可以实现协作式多任务,协程可以把控制器让步给中心调度程序,从而激活其他的协程。

  • .throw() 让调用方抛出异常,在生成器中处理
  • .close() 终止生成器
1
2
3
4
def simple_coroutine():
print("start")
x = yield
print("received x")

yield 左边是接受的值(send),yield右边是产生的值。首先要激活协程:next(my_coroutine)或者my_coroutine.send(None),其中my_coroutine = simple_coroutine()

协程在yield关键字所在的位置暂停执行,= 右边的代码在赋值之前执行,因此对于b = yield a所以等到客户端代码再次激活协程的时候才会设定b的值,但是在上次激活协程的时候已经将a的值生成返回给了客户端。

计算移动平均值的协程

1
2
3
4
5
6
7
8
9
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count

使用协程之前必须预激

1
2
3
coro_avg = averager()
next(coro_avg)
coro_avg.send(10)

一个预激的装饰器

1
2
3
4
5
6
7
8
from functools import wraps
def coroutine(func):
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen
return primer

yield from调用协程的时候,会自动预激,asyncio.coroutine装饰器不会预激协程,所以可以兼容yield from句法。

终止协程和异常处理

协程中未处理的异常会向上冒泡,传给next函数或者send方法的调用方,即触发协程的对象

使用throw和close方法来关闭协程

  • throw: 生成器在暂停的yield表达式处抛出指定的异常,如果生成器处理了异常,代码会执行到下一个yield表达式,产生出的值就是调用throw方法得到的返回值
  • close: 致使生成器在暂停的yield表达式处抛出GeneratorExit异常,收到该异常,生成器一定不能产出值。

一旦出现未处理的异常,协程会立即终止。

yield from结构可以将异常传入嵌套的协程,另外可以让协程更好的返回值。

让协程返回值

某些协程不会产出值,而是在最后返回一个值

1
2
3
4
5
6
7
8
9
10
11
12
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total / count
return Result(count, average)

如果send方法传递了None,那么生成器会抛出StopIteration异常,异常对象的value属性保存着返回的值。

1
2
3
4
try:
coro_avg.send(None)
except StopIteration as exc:
result = exc.value

yield from结构会在内部捕获StopIteration异常,还会将value属性的值变成yield from表达式的值。不过在函数外部使用yield from,会出错。

使用yield from

yield from x表达式对x对象所做的事情是:调用iter(x),从中获取迭代器,因此x可以是任意可以迭代的对象。

1
2
3
def gen():
yield from 'AB'
yield from range(1, 3)

yield from的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者就可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程添加大量处理异常的样板代码,有了这个结构,协程可以通过以前不可能的方式委托职责。

  • 委派生成器:包含yield from iterable 表达式的生成器函数
  • 子生成器:从yield from iterable中iterable部分获取的生成器
  • 调用方:指代调用委派生成器的客户端代码

引入yield from的结构的目的是为了支持实现了__next__,send,close和throw方法的生成器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from collections import namedtuple
Result = namedtuple('Result', 'count average')
# 子生成器
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total / count
return Result(count, average)
# 委派生成器
def grouper(results, key):
while True:
results[key] = yield from averager()
# 客户端代码,调用方
def main(data):
results = {}
for key, values in data.items():
group = grouper(results, key)
next(group)
for value in values:
group.send(value)
group.send(None)
print(results)
data = {
'girls:kg': [40, 50]
}
main(data)

委派生成器相当于管道,所以可以把任意数量个委派生成器链接在一起,一个委派生成器使用yield from调用一个子生成器,而那个子生成器本身也是委派生成器,使用yield from调用另一个子生成器,最终,这个链条会以一个只使用yield表达式的简单生成器结束,也能以任何可迭代的对象结束

任何yield from链条都必须由客户驱动,在最外层委派生成器上调用next或者send,可以隐式调用,比如使用for循环

yield from的意义

把迭代器当作生成器使用,相当于把子生成器的定义体内联在yield from表达式中,子生成器可以执行return语句,返回一个值,返回的值会成为yield from表达式的值。

yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数

  • 传入委派生成器的异常,除了GeneratorExit之外都传给子生成器的throw方法
  • 如果把GeneratorExit异常传入委派生成器,那么在子生成器上调用close方法

yield from foo句法能够防止阻塞,是因为当前协程(包含yield from代码的委派生成器)暂停后,控制权回到事件循环手中,再去驱动其他协程。foo期物或者协程运行完毕后,把结果返回给暂停的协程,将其恢复。