生成器-generator

        您可能据说过,带有 yield 的函数在 Python 中被称之为 generator(生成器),何谓 generator ?html

咱们先抛开 generator,以一个常见的编程题目来展现 yield 的概念。node

        如何产生斐波拉契数列?python

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:一、一、二、三、五、八、1三、2一、3四、……在数学上,斐波纳契数列以以下被以递归的方法定义:F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
程序员

        许多初学者均可以写出下面的函数:编程

方法一:数据结构

1 # 生成斐波那契数列前n个元素
2 def fab(n): 3     i = 0 4     a, b = 0, 1
5     while i < n: 6         print(a) 7         a, b = b, a + b 8         i += 1

执行 fab(6) 得出前6个元素:app

0 1
1
2
3
5

结果没有问题,但函数的返回值为None,别的函数没法获取结果,复用性不好,函数

要提升 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List。如下是 fab 函数改写后的第二个版本:spa

方法二:code

 1 # 生成斐波那契数列前n个元素,以list返回
 2 def fab(n):  3     i = 0  4     a, b = 0, 1
 5     list_fab = []  6     while i < n:  7         # print(a)
 8  list_fab.append(a)  9         a, b = b, a + b 10         i += 1
11 
12     return list_fab

可使用以下方式打印出 fab 函数返回的 List:

1 for item in fab(6): 2     print(item)

改写后的 fab 函数经过返回 List 能知足复用性的要求,可是更有经验的开发者会指出,该函数在运行中占用的内存会随着参数 n的增大而增大,若是要控制内存占用,最好不要用 List,来保存中间结果,而该经过 iterable 对象来迭代,这时yield就派上用场了。

第三个版本的 fab 和初版相比,仅仅把 print b 改成了 yield b,就在保持简洁性的同时得到了 iterable 的效果。

方法三:

1 # 使用 yield替代print
2 def fab(n): 3     i = 0 4     a, b = 0, 1
5     while i < n: 6         # print(a)
7         yield a 8         a, b = b, a + b 9         i += 1

简单地讲,yield 的做用就是把一个函数变成一个 generator,带有 yield 的函数再也不是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(6) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield a 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield a 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是彻底同样的,因而函数继续执行,直到再次遇到 yield。

for item in fab(6): print(item)

也能够手动调用 fab(6) 的 next() 方法(由于 fab(6) 是一个 generator 对象,该对象具备 next() 方法),这样咱们就能够更清楚地看到 fab 的执行流程:

1 f = fab(6) 2 print(next(f)) 3 print(next(f)) 4 print(next(f)) 5 print(next(f)) 6 print(next(f)) 7 print(next(f)) 8 print(next(f))

当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

咱们能够得出如下结论:

一个带有 yield 的函数就是一个 generator,它和普通函数不一样,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程当中被 yield 中断了数次,每次中断都会经过 yield 返回当前的迭代值。

若是想实现一个自定义方法,与range()相似,咱们能够用生成器来定义它,下面是一个生成范围内浮点数的生成器:

1 def f_range(start, stop, step):
2     f = start
3     while f < stop:
4         yield f
5         f += step

使用这个函数,咱们能够用for循环迭代它:

1 for item in f_range(0, 4, 0.5):
2     print(item)
1 0
2 0.5
3 1.0
4 1.5
5 2.0
6 2.5
7 3.0
8 3.5

或者使用其余接受可迭代对象的方法(内置函数 def sum(iterable, start=None): ,  list(iterable) ):

1 print("sum:", sum(f_range(0, 4, 0.5)))
2 print("list:", list(f_range(0, 4, 0.5)))

探讨:

一个函数中须要有一个yield 语句便可将其转换为一个生成器。跟普通函数不一样
的是,生成器只能用于迭代操做

跟普通函数不一样的是,生成器只能用于迭代操做。下面是一个实验,向你展现这样的函数底层工做机

制:

 1 >>> def countdown(n):
 2 ...     while n > 0 :
 3 ...         print("before yield")
 4 ...         yield n
 5 ...         print("after yield")
 6 ...         n -= 1
 7 ...         
 8 >>> iter_c  = countdown(3)  # Create the generator, notice no output appears
 9 >>> print(iter_c, type(iter_c))
10 <generator object countdown at 0x033D0198> <class 'generator'>
11 >>> next(iter_c)  # Run to first yield and emit a value, but doesn't print "after yield"
12 before yield
13 3
14 >>> next(iter_c)  # Run to next yield
15 after yield
16 before yield
17 2
18 >>> next(iter_c)  # Run to next yield
19 after yield
20 before yield
21 1
22 >>> next(iter_c)  # Run to next yield (iteration stops)
23 after yield
24 Traceback (most recent call last):
25   File "<input>", line 1, in <module>
26 StopIteration

一个生成器函数主要特征是它只会回应在迭代中使用到的next 操做。一旦生成器
函数返回退出,迭代终止。咱们在迭代中一般使用的for 语句会自动处理这些细节,所
以你无需担忧。

 

反向迭代:

不少程序员并不知道能够经过在自定义类上实现reversed () 方法来实现反向迭代。好比:

 1 class CountDown(object):
 2     def __init__(self, start):
 3         self.__start = start
 4 
 5     def __iter__(self):  # Forward iterator
 6         n = self.__start
 7         while n >= 0:
 8             yield n
 9             n -= 1
10 
11     def __reversed__(self):  # Reverse iterator
12         n = 0
13         while n <= self.__start:
14             yield n
15             n += 1
16 
17 c1 = CountDown(5)
18 for item in c1:
19     print(item)
20 
21 for item in reversed(CountDown(5)):
22     print(item)

定义一个反向迭代器可使得代码很是的高效,由于它再也不须要将数据填充到一个
列表中而后再去反向迭代这个列表。

 

在迭代器一文中 咱们使用Node类表示树形数据结构,咱们要实现深度优先方式遍历树的节点的方法,下面是示例代码:

 1 class Node(object):
 2     def __init__(self, value):
 3         self.__value = value
 4         self.__children = []
 5 
 6     def add_child(self, node):
 7         self.__children.append(node)
 8 
 9     def set_value(self, value):
10         self.__value = value
11 
12     def get_value(self):
13         return self.__value
14 
15     def __repr__(self):
16         return 'Node({v})'.format(v = self.__value)
17 
18     def __iter__(self):
19         return iter(self.__children)
20 
21     def depth_first(self):
22         yield self
23         for child in self:  # Invokes self.__iter__()
24             yield from child.depth_first()  # 将yield from视为提供了一个调用者和子生成器之间的透明的双向通道。包括从子生成器获取数据以及向子生成器传送数据。
# Example
root = Node(10)
n2 = Node(20)
n3 = Node(30)

root.add_child(n2)
root.add_child(n3)
n2.add_child(Node(40))
n2.add_child((Node(50)))
n3.add_child((Node(60)))

for chi in root.depth_first():
    print(chi)

先序遍历过程:

因此最后的输出是:Node(10) Node(20) Node(40) Node(50) Node(30) Node(60)

 

 迭代器切片:

函数itertools.islice() 正好适用于在迭代器和生成器上作切片操做

def count(n):
    while True:
        yield n
        n += 1

it_c = count(0)
it_c[10, 20]

#Output:
#it_c[10, 20]
#TypeError: 'generator' object is not subscriptable

#Use islice
from itertools import islice
for i in islice(count(1), 10, 20):
    print(i)

探讨:迭代器和生成器不能使用标准的切片操做,由于它们的长度事先咱们并不知道(而且也没有实现索引)。函数islice() 返回一个能够生成指定元素的迭代器,它经过遍历并丢弃

直到切片开始索引位置的全部元素。而后才开始一个个的返回元素,并直到切片结束索引位置。这里要着重强调的一点是islice() 会消耗掉传入的迭代器中的数据。必须考虑到迭代器是不可逆的这个事实。因此若是你须要以后再次访问这个迭代器的话,那你就得先将它里面的数据放入一个列表中。

 

#Note:本文参考另一篇博文

原文连接:http://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

相关文章
相关标签/搜索