Python生成器(generator)并非一个晦涩难懂的概念。相比于MetaClass和Closure等概念,其较为容易理解和掌握。但相对于程序结构:顺序、循环和分支而言其又不是特别的直观。不管学习任何的东西,概念都是很是重要的。正确树立并掌握一些基础的概念是灵活和合理运用的前提,本文将以一种通俗易懂的方式介绍一下generator和yield表达式。python
首先明白两点:缓存
比较常见的数据类型list、tuple、dict等都是可迭代的,属于collections.Iterable类型;app
迭代器不只可迭代还能够被内置函数next调用,属于collections.Iterator类型;ide
迭代器是特殊的可迭代对象,是可迭代对象的一个子集。函数
将要介绍的gererator(生成器)是types.GeneratorType类型,也是collections.Iterator类型。学习
也就是说生成器是迭代器,可被next调用,也可迭代。测试
三者的包含关系:(可迭代(迭代器(生成器)))ui
python有两种类型的生成器:生成器表达式和生成器函数。spa
因为生成器可迭代而且是iterator,所以能够经过for和next进行遍历。code
把列表生成式的[]改为()便获得生成器表达式。
>>> 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 >>>
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返回时会保留当前函数的执行状态,再次被调用时能够从中断的地方继续执行。
经过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
简单分析一下执行过程:
若是在上面代码后面再加一行:
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。缘由后面解释。
使用了yield的函数严格来说已经不是一个函数,而是一个生成器。所以函数中yield和return是不能同时出现的。
SyntaxError: 'return' with argument inside generator
生成器只能经过yield将每次调用的结果返回给调用者。
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可能会致使混乱的遍历流程。
其实到这里生成器相关的概念基本已经介绍完成了,本身动手过一遍应该能弄明白了。为了更加深入的体会生成器,下面咱们在往前走一步。
在Python 2中这两个比较经常使用,看一下二者的区别:
这里xrange有点相似于上面介绍的生成器表达式,虽然xrange返回的并非生成器,但二者均返回并不包含所有结果可迭代对象。
做为一个iterator:
The iterator objects themselves are required to support the following two methods, which together form the iterator protocol:
iterator.
__iter__
()Return the iterator object itself. This is required to allow both containers and iterators to be used with the
for
andin
statements. This method corresponds to thetp_iter
slot of the type structure for Python objects in the Python/C API.
iterator.
next
()Return the next item from the container. If there are no further items, raise the
StopIteration
exception. This method corresponds to thetp_iternext
slot of the type structure for Python objects in the Python/C API.
下面咱们自定义class my_xrange:
1 class my_xrange(object): 2 def __init__(self, start, stop = None, step = 1): 3 """ 仅仅为了演示,假设start, stop 和 step 均为正整数 """ 4 self._start = 0 if stop is None else start 5 self._stop = start if stop is None else stop 6 self._step = step 7 self._cur_val = self._start 8 9 def __iter__(self): 10 return self 11 12 def next(self): 13 if self._start <= self._cur_val < self._stop: 14 cur_val = self._cur_val 15 self._cur_val += self._step 16 return cur_val 17 raise StopIteration
测试结果:
import collections myxrange = my_xrange(0, 10, 3) res = [] for val in myxrange: res.append(val) print res == range(0, 10, 3) # True print isinstance(myxrange, collections.Iterator) # True
print isinstance(myxrange, types.GeneratorType) # False
下面使用函数生成器定义一个generator版的xrange。
def xrange_func(start, stop, step = 1): """ 仅仅为了演示,假设start, stop 和 step 均为正整数 """ cur_val = start while start <= cur_val and cur_val < stop: yield cur_val cur_val += step
isinstance(myxrange, collections.Iterator) and isinstance(myxrange, types.GeneratorType) is True
上面两个自定义xrange版本的例子,均说明生成器以及迭代器保留数列生成过程的状态,每次只计算一个值并返回。这样只要占用不多的内存便可表示一个很大的序列。
不论是迭代器仍是生成器,对于有大量有规律的数据产生并须要遍历访问的情景均适用,占用内存少并且遍历的速度快。其中一个较为经典的应用为斐波那契数列(Fibonacci sequence)。
这里以os.walk遍历目录为例来讲明yield的应用。若是咱们须要遍历一个根目录下的全部文件并根据须要进行增删改查。可能会遇到下列的问题:
预先遍历且缓存结果,可是目录下文件可能不少,并且会动态改变;若是不缓存,多个地方可能会频繁的须要访问这一结果致使效率低下。
这时候可使用yield定义一个生成器函数。
def get_all_dir_files(target_dir): for root, dirs, files in os.walk(target_dir): for file in files: file_path = os.path.join(root, file) yield os.path.realpath(file_path) def file_factory(file): """ do something """ target_dir = './' all_files = get_all_dir_files(target_dir) for file in all_files: file_factory(file)
限于篇幅,就先介绍到这里,但愿本文能让你对生成器有一个新的认识。