知道迭代器之后,就可以正式进入生成器的话题了。普通函数用 return 返回一个值,和 Java 等其他语言是一样的,然而在 Python 中还有一种函数,用关键字 yield 来返回值,这种函数叫生成器函数,函数被调用时会返回一个生成器对象,生成器本质上还是一个迭代器,也是用在迭代操作中,因此它有和迭代器一样的特性,唯一的区别在于实现方式上不一样,后者更加简洁
>>> def gen(n):
... yield n**2
...
>>> from collections import Iterator
>>> g = gen(10)
>>> type(g)
<class 'generator'>
>>> isinstance(g, Iterator)
True
- 语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值
- 生成器本质上还是一个迭代器。
- 状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行
Python有两种不同的方式提供生成器:
- 生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
- 生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
最简单的生成器函数:
生成器函数
xrange不是生成器
函数返回的是一个xrange obj。xrange类型是一种不可变的序列,通常用于循环。xrange类型的优点是,xrange对象总是使用相同数量的内存,不管它所代表的范围大小。所以xrange比range更好。(Python3中变为range, xrange也变为range对象了。)
In [28]: I = xrange(10)
In [29]: isinstance(I, Iterable)
Out[29]: True
In [30]: isinstance(I, Iterator)
Out[30]: False
自定义生成器
以生成斐波那契数列序列为例
- 不使用生成器
def fib(n):
pre, cur = 0, 1
result = []
if n == 0:
return result
elif n == 1:
return [1]
elif n > 1:
result.append(cur)
for i in range(n - 1):
pre, cur = cur, pre + cur
result.append(cur)
return result
if __name__ == '__main__':
for i in fib(10):
print(i, end=' ')
print()
- 使用生成器
def fib(n):
prev, curr = 0, 1
while n > 0:
n -= 1
yield curr
prev, curr = curr, curr + prev
if __name__ == '__main__':
for i in fib(10):
print(i, end=' ')
print()
运行结果都一样:
1 1 2 3 5 8 13 21 34 55
[Finished in 0.1s]
很显然使用生成器比不使用更加简洁。
生成器表达式
生成器表达式与列表推导式长的非常像,但是它俩返回的对象不一样,前者返回生成器对象,后者返回列表对象。
>>> g = (x*2 for x in range(10))
>>> type(g)
<type 'generator'>
>>> l = [x*2 for x in range(10)]
>>> type(l)
<type 'list'>
这样并不能看出什么,在Ipython中测试一下性能
In [33]: %timeit (x*2 for x in range(1000000))
100 loops, best of 3: 16.5 ms per loop
In [34]: %timeit [x*2 for x in range(1000000)]
10 loops, best of 3: 98.5 ms per loop
可以看到生成器表达式更为高效!
生成器支持的方法
>>> help(fib(10))
fib = class generator(object)
| Methods defined here:
|
| __del__(...)
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __iter__(self, /)
| Implement iter(self).
|
| __next__(self, /)
| Implement next(self).
|
| __repr__(self, /)
| Return repr(self).
|
| close(...)
| close() -> raise GeneratorExit inside generator.
|
| send(...)
| send(arg) -> send 'arg' into generator,
| return next yielded value or raise StopIteration.
|
| throw(...)
| throw(typ[,val[,tb]]) -> raise exception in generator,
| return next yielded value or raise StopIteration.
|
| ----------------------------------------------------------------------
close
手动关闭生成器函数,后面的调用会直接返回StopIteration异常。
>>> def g4():
... yield 1
... yield 2
... yield 3
...
>>> g=g4()
>>> next(g)
1
>>> g.close()
>>> next(g) #关闭后,yield 2和yield 3语句将不再起作用
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
send
生成器函数最大的特点是可以接受外部传入的一个变量,并根据变量内容计算结果后返回。
这是生成器函数最难理解的地方,也是最重要的地方,实现后面我会讲到的协程就全靠它了。
def gen():
value=0
while True:
receive=yield value
if receive=='e':
break
value = 'got: %s' % receive
g=gen()
print(g.send(None))
print(g.send('aaa'))
print(g.send(3))
print(g.send('e'))
执行流程:
- 通过g.send(None)或者next(g)可以启动生成器函数,并执行到第一个yield语句结束的位置。此时,执行完了yield语句,但是没有给receive赋值。yield value会输出初始值0注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。
- 通过g.send(‘aaa’),会传入aaa,并赋值给receive,然后计算出value的值,并回到while头部,执行yield value语句有停止。此时yield value会输出”got: aaa”,然后挂起。
- 通过g.send(3),会重复第2步,最后输出结果为”got: 3″
- 当我们g.send(‘e’)时,程序会执行break然后推出循环,最后整个函数执行完毕,所以会得到StopIteration异常。
最后的执行结果如下:
0
got: aaa
got: 3
Traceback (most recent call last):
File "h.py", line 14, in <module>
print(g.send('e'))
StopIteration
throw
用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。
throw()后直接跑出异常并结束程序,或者消耗掉一个yield,或者在没有下一个yield的时候直接进行到程序的结尾。
def gen():
while True:
try:
yield 'normal value'
yield 'normal value 2'
print('here')
except ValueError:
print('we got ValueError here')
except TypeError:
break
g=gen()
print(next(g))
print(g.throw(ValueError))
print(next(g))
print(g.throw(TypeError))
输出结果为:
normal value
we got ValueError here
normal value
normal value 2
Traceback (most recent call last):
File "h.py", line 15, in <module>
print(g.throw(TypeError))
StopIteration
生成器挂起
yield在生成一个值之后会扶起当前线程
利用多个CPU只有两种方法
- 利用多个进程分享CPU
- 一个进程,创建多个进程
Python支持吗?
- Python支持
- 但是并不是真正的支持
GIL锁:Python由于众所周知的GIL的原因,导致其线程无法发挥多核的并行计算能力(当然,后来有了multiprocessing,可以实现多进程并行),显得比较鸡肋。既然在GIL之下,同一时刻只能有一个线程在运行,那么对于CPU密集的程序来说,线程之间的切换开销就成了拖累,而以I/O为瓶颈的程序正是协程所擅长的:
协程(脚本语言的开发者发明的)
- 模拟并行
- 消息机制
协程比多线程快的原因是:线程切换耗费时间;
生成器:
# encoding:utf-8
def generator():
m = []
while True:
# 返回m值,并挂起函数,暂停执行,此时赋值语句尚未完成
x = yield m
m.append(x)
if __name__ == '__main__':
g = generator()
print(g)
print(g.send(None))
print(g.send(1))
print(g.send(2))
运行结果:
<generator object generator at 0x00000000025CBE10>
[]
[1]
[1, 2]
[Finished in 0.1s]
消费者生产者模型:
# encoding:utf-8
'''消费者生产者模型
生产者 -- 生产东西 -- 主程 -- 不能写死循环
消费者 -- 消费东西 -- 协程 -- 往往写死循环
'''
def producer(c):
c.send(None)
n = 0
while n < 5:
n += 1
print('生产者,生产了... %s' % n)
r = c.send(n)
print('生产者, 收到了回馈... %s' % r)
c.close()
def consumer():
r = ''
while True:
n = yield r
if not n:
return
else:
print('消费者,消费了... %s' % n)
if __name__ == '__main__':
g = generator()
print(g)
print(g.send(None))
print(g.send(1))
print(g.send(2))
# c = consumer()
# producer(c)
运行结果:
生产者,生产了... 1
消费者,消费了... 1
生产者, 收到了回馈...
生产者,生产了... 2
消费者,消费了... 2
生产者, 收到了回馈...
生产者,生产了... 3
消费者,消费了... 3
生产者, 收到了回馈...
生产者,生产了... 4
消费者,消费了... 4
生产者, 收到了回馈...
生产者,生产了... 5
消费者,消费了... 5
生产者, 收到了回馈...
[Finished in 0.1s]
总结
- 按照鸭子模型理论,生成器就是一种迭代器,可以使用for进行迭代。
- 第一次执行next(generator)时,会执行完yield语句后程序进行挂起,所有的参数和状态会进行保存。再一次执行next(generator)时,会从挂起的状态开始往后执行。在遇到程序的结尾或者遇到StopIteration时,循环结束。
- 可以通过generator.send(arg)来传入参数,这是协程模型。
- 可以通过generator.throw(exception)来传入一个异常。throw语句会消耗掉一个yield。可以通过generator.close()来手动关闭生成器。
- next()等价于send(None)