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 则可以用于向生成器函数发送消息.
- def yield_func():
- for i in xrange(1, 3):
- x = yield i
- print 'yield_func',x
- gen_func = yield_func()
- print 'iter result: %d' % next(gen_func)
- 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 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 and in statements. This method corresponds to the tp_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 the tp_iternext slot of the type structure for Python objects in the Python/C API.
下面我们自定义 class my_xrange:
- class my_xrange(object):
- def __init__(self, start, stop = None, step = 1):
- """仅仅为了演示, 假设 start, stop 和 step 均为正整数"""
- self._start = 0 if stop is None else start
- self._stop = start if stop is None else stop
- self._step = step
- self._cur_val = self._start
- def __iter__(self):
- return self
- def next(self):
- if self._start <= self._cur_val < self._stop:
- cur_val = self._cur_val
- self._cur_val += self._step
- return cur_val
- 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
3.2 使用函数生成器
下面使用函数生成器定义一个 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 版本的例子, 均说明生成器以及迭代器保留数列生成过程的状态, 每次只计算一个值并返回. 这样只要占用很少的内存即可表示一个很大的序列.
4. 应用
不管是迭代器还是生成器, 对于有大量有规律的数据产生并需要遍历访问的情景均适用, 占用内存少而且遍历的速度快. 其中一个较为经典的应用为斐波那契数列(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)
限于篇幅, 就先介绍到这里, 希望本文能让你对生成器有一个新的认识.
来源: https://www.cnblogs.com/yssjun/p/10236126.html