Generator(生成器)
在Python中,说到generator,就不得不提iterator和iterable,下面这张图来自Iterables vs. Iterators vs. Generators,这里做个简单的说明(文章很好的解释了这三者的关系与区别)。
- Container:是一个把元素组织在一起的数据结构,可以判断元素是否包含在容器当中。(大多数)容器提供了一种能够得到他们包含的每个元素的方法,这使得这些容器是iterable(可迭代对象)。
- Iterable:iterable是任何一个可以返回iterator的对象(不限于容器)。
- Iterator:带状态的对象,当对这个对象调用
next()
时,可以产生下一个值。任何有__next__()
方法的对象都是一个iterator。Iterator就像一个lazy factory,当需要时,才产生一个值返回。
下面是一个iterator的例子,与此同时也是一个iterable,
1
2
3
4
5
6
7
8
9
10
11
12
13
| class fib:
def __init__(self):
self.prev = 0
self.curr = 1
def __iter__(self):
return self
def __next__(self):
value = self.curr
self.curr += self.prev
self.prev = value
return value
|
下面着重要说的是generator,genarator是一种特殊的iterator,因此它是一个惰性求值的factory。继续上面的例子,generator能够避免编写__iter__()
和__next__()
,而以一种简洁优雅的方式写出上面的fib
iterator。
1
2
3
4
5
6
7
8
| def fib():
prev, curr = 0, 1
while True:
yield curr
prev, curr = curr, prev + curr
f = fib()
list(islice(f, 0, 10))
|
当执行fib()
时,实例化并返回了一个generator,除此之外什么代码都没有被执行,包括prev, curr = 0, 1
。islice
是一个iterator,因此fib
的代码仍然未被执行。
而list
会使用其参数,由其来构造一个list,它会对islice
对象调用next()
,进而会对f
对象调用next()
,此时才开始执行fib()
里的代码,直至yield curr
,返回curr
中的值,并暂停执行fib()
,fib()
的状态被冻结了。这个值被返回给islice
,最终被添加到list里面。然后重复上述过程,直到产生第10个元素。
generator的类型有函数generator和表达式generator,表达式generator语法类似列表生成式,
1
2
3
4
5
| numbers = [1, 2, 3, 4, 5, 6]
square_list = [x * x for x in numbers]
square_set = {x * x for x in numbers}
lazy_square_list = (x * x for x in numbers)
|
到目前为止,似乎generator相比起iterator,除了更简洁以外,没有什么特别的东西。pep-0342(A new method for generator-iterators is proposed, called send(). It
takes exactly one argument, which is the value that should be “sent in” to the generator).规定了一个genarator可以产生一个值,或者在产生一个值的同时还接收一个值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| def fib():
prev, curr = 0, 1
while True:
old_curr = curr
curr = yield old_curr
if not curr is None and not curr == old_curr:
prev, curr = curr, curr + 1
else:
prev, curr = old_curr, prev + old_curr
f = fib()
list(islice(f, 0, 10)) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
f.send(100)
list(islice(f, 0, 10)) # [201, 302, 503, 805, 1308, 2113, 3421, 5534, 8955, 14489]
|
第二次的list(islice(f, 0, 10))
结果不是[89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
,因为send()
改变了curr的值。
在调用send()
前,由于还未执行到yield
处,因此必须先调用一次next()
或send(None)
。
Coroutines(协程)
与Coroutines对应的概念是Subroutine(子程序)。一个普通的函数调用是这样的,从函数的第一行执行到return
语句或exception,或者执行到函数的结尾,这样也叫做一个subroutine。但有时候又希望函数能够生成一系列的值,而不仅仅是返回一个值,此时函数不应该return(return control of execution),而是yield(transfer control temporarily and voluntarily),因为函数需要稍后继续执行。generator能够冻结函数的状态,继续执行的时候恢复。
下面是用generator来实现的一个生产者-消费者模型,
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
| import random
def consume():
consumed_count = 0
while True:
data = yield
consumed_count += 1
print('Consuming {}, Total Consumed {}'.format(data, consumed_count))
def produce(consumer):
while True:
data = random.random()
print('Produced {}'.format(data))
consumer.send(data)
yield
consumer = consume()
consumer.send(None) # or consumer.next()
producer = produce(consumer)
for _ in range(10):
print('Producing...')
next(producer)
# Producing...
# Produced 0.4138693968479813
# Consuming 0.4138693968479813, Total Consumed 1
# Producing...
# Produced 0.5462849666609885
# Consuming 0.5462849666609885, Total Consumed 2
# Producing...
# Produced 0.06190270111408913
# Consuming 0.06190270111408913, Total Consumed 3
|
References
- 谈谈Python的生成器 里有一个关于
send
不错的例子 - Improve Your Python: ‘yield’ and Generators Explained