开始前先认识下生成器,即generator
创建列表可以使用列表生成式:
>>>l = [v**2 for v in range(1,10)]
>>>l
[1, 4, 9, 16, 25, 36, 49, 64, 81]
但如果要创建的列表有百万甚至千万个元素,用到的元素只有前几个,考虑到内存的因素我们肯定不能直接创建,生成器就很好的解决了这个问题,生成器不会直接创建整个列表,而是在迭代的过程中推算出每个元素的值,是惰性的。
把列表生成式的[]
的换成()
就是一个简单的生成器:
>>>g = (v**2 for v in range(1,10))
>>>g
<generator object <genexpr> at 0x000000000220F1A8>
要取出生成器每一个元素可以通过next()
函数:
当没有元素可取时会抛出一个
StopIteration
异常,需要自行捕获处理。如果有成千上万个元素,一直
next()
当然不行了,由于生成器是可迭代的,可以用for
循环来处理,还有一个好处就是不用考虑StopIteration
异常:
>>>for v in g:
... print(n)
可以使用isinstance()
判断一个对象是否是可迭代对象(Iterable
):
>>>from collections import Iterable
>>>isinstance(g, Iterable)
True
除了用()
创建生成器,如果一个函数定义中有yield
关键字,则这个函数也是一个生成器:
def test():
print('step-1')
yield 1
print('step-2')
yield 2
print('step-3')
yield 3
>>>g = test()
>>>g
<generator object test at 0x00000000027EF1A8>
所以同样可以用next()
或者for
循环来迭代它。当每次执行next(g)
时,生成器函数遇到yield
表达式就会中断并返回yield
表达式后边的参数值,例如:
>>>next(g)
step-1
1
再次执行时从上次返回的yield
表达式处继续执行,返回下一个yield
表达式的参数值,直到再无yield
表达式参数可返回并抛出异常:
>>>next(g)
step-2
2
>>>next(g)
step-3
3
除了next()
外,还有一个很重要的函数send()
,它和next()
作用类似,但是send()
可以发送值给对应的yield
表达式,我们之前执行next(g)
就相当于g.send(None)
。
注意第一次调用next()
或send(None)
相当于启动生成器,不能使用send()
发送一个非None
的值,否则会出错的(TypeError: can't send non-None value to a just-started generator
),因为还没有yield
表达式来接收这个值。
那么启动生成器后,用send()
发送的值如何被生成器中yield
表达式接收呢?先修改上边的生成器函数:
def test():
print('step-1')
x = yield 1
print(x)
print('step-2')
y = yield 2
print(y)
print('step-3')
x = yield 3
我们打印了前两个yield
表达式的值。接下来通过send()
方式先启动生成器:
>>>g = test()
>>>g.send(None)
step-1
1
仅仅是打印了提示语和返回了第一个yield
表达式的参数值。再继续执行:
>>>g.send('hello')
hello
step-2
2
可以看到先打印了用send()
发送的值,然后是提示语和第二个yield
表达式的参数值,所以这次send()
发送的值被第一个yield
表达式接收了,即yield 1
表达式被赋值为hello
,即x = 'hello'
。再继续执行:
>>>g.send('world')
world
step-3
3
即第二个yield 2
表达式被赋值为world
,并返回了第三个yield
表达式的参数值。
所以每次执行send()
或next()
只是返回了对应yield
表达式的参数值,其实对应表达式并未执行,直到下次再执行send()
或next()
才会执行上次返回参数的yield
表达式,所谓的执行yield
表达式就是给其赋值,并返回下一个yield
表达式的参数值!