Python generator和yield介绍

Python生成器(generator)并非一个晦涩难懂的概念。相比于MetaClass和Closure等概念,其较为容易理解和掌握。但相对于程序结构:顺序、循环和分支而言其又不是特别的直观。不管学习任何的东西,概念都是很是重要的。正确树立并掌握一些基础的概念是灵活和合理运用的前提,本文将以一种通俗易懂的方式介绍一下generator和yield表达式。python

1. Iterator与Iterable

首先明白两点:缓存

  1. Iterator(迭代器)是可迭代对象;
  2. 可迭代对象并不必定是Iterator;

比较常见的数据类型list、tuple、dict等都是可迭代的,属于collections.Iterable类型;app

迭代器不只可迭代还能够被内置函数next调用,属于collections.Iterator类型;ide

迭代器是特殊的可迭代对象,是可迭代对象的一个子集。函数

将要介绍的gererator(生成器)是types.GeneratorType类型,也是collections.Iterator类型。学习

也就是说生成器是迭代器,可被next调用,也可迭代。测试

三者的包含关系:(可迭代(迭代器(生成器)))ui

2. Python生成器

python有两种类型的生成器:生成器表达式和生成器函数。spa

因为生成器可迭代而且是iterator,所以能够经过for和next进行遍历。code

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 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:

 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

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)

  限于篇幅,就先介绍到这里,但愿本文能让你对生成器有一个新的认识。

相关文章
相关标签/搜索