python yield 与生成器

2018-10-23 18:25:09   最后更新: 2018-10-23 18:25:09   访问数量:87




此前的日志中,我们了解了 python 的循环语句

python 条件与循环语句

本篇日志中,我们通过 yield 关键字来优化循环的执行

 

斐波那契数列是一个非常简单的递归数列,除第一个和第二个数外,任意一个数都可由前两个数相加得到

下面是使用 python 实现的一个斐波那契序列函数:

def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L

 

 

如下调用可以看到返回结果:

>>> for n in fab(5): ... print n ... 1 1 2 3 5

 

 

如果我们的 max 增大,返回的列表占用的空间将显著提升,这显然是我们不希望看到的

 

上一篇日志中,我们介绍了迭代器,迭代器可以解决上面提到的问题

下面我们定义一个迭代器:

class Fab(object): def __init__(self, max): self.max = max self.n, self.a, self.b = 0, 0, 1 def __iter__(self): return self def next(self): if self.n < self.max: r = self.b self.a, self.b = self.b, self.a + self.b self.n = self.n + 1 return r raise StopIteration()

 

 

Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数:

>>> for n in Fab(5): ... print n ... 1 1 2 3 5

 

但是,显然,迭代器版本的代码非常复杂

 

def fab(max): n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1

 

 

我们可以非常方便的遍历 fab 函数返回的结果:

>>> for n in fab(5): ... print n ... 1 1 2 3 5

 

 

带有 yield 的函数在 Python 中被称之为 generator -- 生成器,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象

在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个 GeneratorType 类型的迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield

既然 yield 函数返回的是一个迭代器,我们当然也可以执行它的 next 方法:

>>> f = fab(5) >>> f.next() 1 >>> f.next() 1 >>> f.next() 2 >>> f.next() 3 >>> f.next() 5 >>> f.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration

 

 

yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰

 

我们可以利用 isgeneratorfunction 判断一个函数是否是一个生成器:

>>> from inspect import isgeneratorfunction >>> isgeneratorfunction(fab) True

 

 

如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用

好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

def read_file(fpath): BLOCK_SIZE = 1024 with open(fpath, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return

 

 

生成器也可以嵌套使用:

#!/bin/python3 def localRange(n): for i in ('a', 'b', 'c'): yield i def feb(max): n, a, b = 0, 0, 1 while n < max: yield from localRange(n) yield b a, b = b, a + b n = n + 1 if __name__ == '__main__': for i in feb(5): print(i)

 

 

执行脚本输出了:

a b c 1 a b c 1 a b c 2 a b c 3 a b c 5

 

 






技术帖      龙潭书斋      python      生成器      generitor     


京ICP备15018585号