相关知识点
生成器
带有 yield 关键字的的函数在 Python 中被称之为 generator(生成器)。Python 解释器会将带有 yield 关键字的函数视为一个 generator 来处理。一个函数或者子程序都只能 return 一次,但是一个生成器能暂停执行并返回一个中间的结果 —— 这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行。
EXAMPLE:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
In [94]: def fab(max): ...: n, a, b = 0, 0, 1 ...: while n < max: ...: yield b ...: a, b = b, a + b ...: n = n + 1 ...: In [95]: f = fab(5) In [96]: f.next() Out[96]: 1 In [97]: f.next() Out[97]: 1 In [98]: f.next() Out[98]: 2 In [99]: f.next() Out[99]: 3 In [100]: f.next() Out[100]: 5 In [101]: f.next() --------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-101-c3e65e5362fb> in <module>() ----> 1 f.next() StopIteration: |
生成器 fab()
的执行过程
执行语句 f = fab(5)
时,并不会马上执行 fab()
函数的代码块,而是首先返回一个 iterable 对象!
在 for 循环语句执行时,才会执行 fab()
函数的代码块。
执行到语句 yield b
时,fab()
函数会返回一个迭代值,直到下次迭代前,程序流会回到 yield b
的下一条语句继续执行,然后再次回到 for 循环,如此迭代直到结束。看起来就好像一个函数在正常执行的过程中被 yield
中断了数次,每次中断都会通过 yield
返回当前的迭代值。
由此可以看出,生成器通过关键字 yield
不断的将迭代器返回到内存进行处理,而不会一次性的将对象全部放入内存,从而节省内存空间。从这点看来生成器和迭代器非常相似,但如果更深入的了解的话,其实两者仍存在区别。
生成器和迭代器的区别
生成器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素,即无须将对象的所有元素都存入内存之后,才开始进行操作。生成器仅在迭代至某个元素时才会将该元素放入内存,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的类序列对象,EG. 大文件/大集合/大字典/斐波那契数列等。这个特点被称为 延迟计算 或 惰性求值(Lazy evaluation),可以有效的节省内存。惰性求值实际上是现实了协同程序 的思想。
协同程序:是一个可以独立运行的函数调用,该调用可以被暂停或者挂起,之后还能够从程序流挂起的地方继续或重新开始。当协同程序被挂起时,Python 就能够从该协同程序中获取一个处于中间状态的属性的返回值(由 yield 返回),当调用 next()
方法使得程序流回到协同程序中时,能够为其传入额外的或者是被改变了的参数,并且从上次挂起的下一条语句继续执行。这是一种类似于进程中断的函数调用方式。这种挂起函数调用并在返回属性中间值后,仍然能够多次继续执行的协同程序被称之为生成器。
NOTE:而迭代器是不具有上述的特性的,不适合去处理一些巨大的类序列对象,所以建议优先考虑使用生成器来处理迭代的场景。
生成器的优势
综上所述:使用生成器最好的场景就是当你需要以迭代的方式去穿越一个巨大的数据集合。比如:一个巨大的文件/一个复杂的数据库查询等。
EXAMPLE 2:读取一个大文件
1 2 3 4 5 6 7 8 9 |
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 |
如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件的部分内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
加强的生成器特性
除了可以使用 next()
方法来获取下一个生成的值,用户还可以使用 send()
方法将一个新的或者是被修改的值返回给生成器。除此之外,还可以使用 close()
方法来随时退出生成器。
EXAMPLE 3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
In [5]: def ="生成器">生成器
带有 yield 关键字的的函数在 Python 中被称之为 generator(生成器)。Python 解释器会将带有 yield 关键字的函数视为一个 generator 来处理。一个函数或者子程序都只能 return 一次,但是一个生成器能暂停执行并返回一个中间的结果 —— 这就是 yield 语句的功能 : 返回一个中间值给调用者并暂停执行。 EXAMPLE:
生成器
|
1 2 3 4 5 6 7 8 9 |
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 |
如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件的部分内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
加强的生成器特性
除了可以使用 next()
方法来获取下一个生成的值,用户还可以使用 send()
方法将一个新的或者是被修改的值返回给生成器。除此之外,还可以使用 close()
方法来随时退出生成器。
EXAMPLE 3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |