Python生成器(generator)并不是一个晦涩难懂的概念。相比于MetaClass和Closure等概念,其较为容易理解和掌握。但相对于程序结构:顺序、循环和分支而言其又不是特别的直观。无论学习任何的东西,概念都是非常重要的。正确树立并掌握一些基础的概念是灵活和合理运用的前提,本文将以一种通俗易懂的方式介绍一下generator和yield表达式。
1. Iterator与Iterable
首先明白两点:
- Iterator(迭代器)是可迭代对象;
- 可迭代对象并不一定是Iterator;
比较常见的数据类型list、tuple、dict等都是可迭代的,属于collections.Iterable类型;
迭代器不仅可迭代还可以被内置函数next调用,属于collections.Iterator类型;
迭代器是特殊的可迭代对象,是可迭代对象的一个子集。
将要介绍的gererator(生成器)是types.GeneratorType类型,也是collections.Iterator类型。
也就是说生成器是迭代器,可被next调用,也可迭代。
三者的包含关系:(可迭代(迭代器(生成器)))
2. Python生成器
python有两种类型的生成器:生成器表达式和生成器函数。
由于生成器可迭代并且是iterator,因此可以通过for和next进行遍历。
2.1 生成器表达式
把列表生成式的[]改成()便得到生成器表达式。
>>> gen = (i + i for i in xrange(10)) >>> gen <generator object <genexpr> at 0x0000000003A2DAB0> >>> type(gen) <type 'generator'> >>> isinstance(gen, types.GeneratorType) and isinstance(gen, collections.Iterator) and isinstance(gen, collections.Iterable) True >>>
2.2 生成器函数
python函数定义中有关键字yield,该函数便是一个生成器函数,函数调用返回的是一个generator.
def yield_func(): for i in xrange(3): yield i gen_func = yield_func() for yield_val in gen_func: print yield_val
生成器函数每次执行到yield便会返回,但与普通函数不同的是yield返回时会保留当前函数的执行状态,再次被调用时可以从中断的地方继续执行。
2.3 next与send
通过for和next可以遍历生成器,而send则可以用于向生成器函数发送消息。
1 def yield_func(): 2 for i in xrange(1, 3): 3 x = yield i 4 print 'yield_func',x 5 gen_func = yield_func() 6 print 'iter result: %d' % next(gen_func) 7 print 'iter result: %d' % gen_func.send(100)
结果:
iter result: 1 yield_func 100 iter result: 2
简单分析一下执行过程:
- line_no 5 调用生成器函数yield_func得到函数生成器gen_func;
- line_no 6 使用next调用gen_func,此时才真正的开始执行yield_func定义的代码;
- line_no 3 执行到yield i,函数yield_func暂停执行并返回当前i的值1.
- line_no 6 next(gen_func)得到函数yield_func执行到yield i返回的值1,输出结果iter result: 1;
- line_no 7 执行gen_func.send(100);
- line_no 3 函数yield_func继续执行,并将调用者send的值100赋值给x;
- line_no 4 输出调用者send接收到的值;
- line_no 3 执行到yield i,函数yield_func暂停执行并返回当前i的值2.
- line_no 7 执行gen_func.send(100)得到函数yield_func运行到yield i返回的值2,输出结果iter result: 2;
如果在上面代码后面再加一行:
print 'iter result: %d' % next(gen_func)
结果:
iter result: 1 yield_func 100 iter result: 2 yield_func None File "G:\Cnblogs\Alpha Panda\Main.py", line 22, in <module> print 'iter result: %d' % next(gen_func) StopIteration
yield_func只会产生2个yield,但是我们迭代调用了3次,会抛出异常StopIteration。
next和send均会触发生成器函数的执行,使用for遍历生成器函数时不要用send。原因后面解释。
2.4 生成器返回值
使用了yield的函数严格来讲已经不是一个函数,而是一个生成器。因此函数中yield和return是不能同时出现的。
SyntaxError: 'return' with argument inside generator
生成器只能通过yield将每次调用的结果返回给调用者。
2.5 可迭代对象转成迭代器
list、tuple、dict等可迭代但不是迭代器的对象可通过内置函数iter转化为iterator,便可以通过next进行遍历;
这样的好处是可以统一使用next遍历所有的可迭代对象;
tup = (1,2,3) for ele in tup: print ele + ele
上面的代码等价于:
tup_iterator = iter(tup)
while True: try: ele = next(tup_iterator) except StopIteration: break print ele + ele
for循环使用next遍历一个迭代器,混合使用send可能会导致混乱的遍历流程。
其实到这里生成器相关的概念基本已经介绍完成了,自己动手过一遍应该能弄明白了。为了更加深刻的体会生成器,下面我们在往前走一步。
3. range与xrange
在Python 2中这两个比较常用,看一下两者的区别:
- range为一个内置函数,xrange是一个类;
- 前者返回一个list,后者返回一个可迭代对象;
- 后者遍历操作快于前者,且占用更少内存;
这里xrange有点类似于上面介绍的生成器表达式,虽然xrange返回的并不是生成器,但两者均返回并不包含全部结果可迭代对象。
3.1 自定义xrange的Iterator版本
作为一个iterator:
The iterator objects themselves are required