python 生成器 Generators
生成器适合在以下场景中使用:
- 处理过大到超出机器内存的数据集
- 有一个复杂的函数,每次调用它时都需要维护一个内部状态,但该函数太小而无法证明创建自己的类是合理的
生成器的特点是不将其内容存储在内存中。生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。
读取大文件
统计文件的行数:
1 | # 第一种方法,一次读入 |
第一种方法中,file.read().split()一次将所有内容加载到内存中,导致MemoryError.
第二种方法,用了yield。执行顺序是,csv_gen = csv_reader_2("some_csv.txt")时,并未跳入csv_reader_2函数!!注意这里还没跳入函数,因为这个函数里面有yield,只是把这个函数赋给一个对象,然后直到for row in csv_gen(等价于next()),才跳入csv_reader_2函数内部,然后执行到yield这条,就返回,然后再遇到for row in csv_gen:,再跳入yield位置的下一条开始执行。
生成无限序列
获得有限序列:
1 | a = range(5) |
然而,生成无限序列需要使用生成器,因为您的计算机内存是有限的
1 | def infinite_sequence(): |
除了使用for循环,您还可以next()直接调用生成器对象。这对于在控制台中测试生成器特别有用:
1 | gen = infinite_sequence() |
在这里,您有一个名为 的生成器gen,您可以通过重复调用 来手动迭代它next()。
注意:当您使用 时next(),Python 会调用.__next__()您作为参数传入的函数。此参数化允许产生一些特殊效果,但这超出了本文的范围。尝试更改传递给的参数next(),看看会发生什么!
示例 3:检测回文
1 | def is_palindrome(num): |
该函数接受一个输入数字,将其反转,并检查反转后的数字是否与原始数字相同。现在您可以使用无限序列生成器来获取所有数字回文的运行列表:
1 | for i in infinite_sequence(): |
了解生成器
生成器函数的外观和行为就像常规函数一样,但具有一个定义特征。生成器函数使用 Pythonyield关键字而不是return. 回想一下您之前编写的生成器函数:
1 | def infinite_sequence(): |
这看起来像一个典型的函数定义,除了 Python yield 语句和它后面的代码。yield指示将值发送回调用方的位置,但与 不同的是return,之后您不会退出该函数。
相反,函数的状态会被记住。这样,当在生成器对象上调用(在循环next()中显式或隐式)时,先前生成的变量会递增,然后再次生成。由于生成器函数看起来像其他函数并且与它们的行为非常相似,因此您可以假设生成器表达式与 Python 中可用的其他理解非常相似。
使用生成器表达式构建生成器
与列表推导式一样,生成器表达式允许您仅用几行代码即可快速创建生成器对象。它们在使用列表理解的相同情况下也很有用,还有一个额外的好处:您可以创建它们而无需在迭代之前构建并将整个对象保存在内存中。换句话说,当你使用生成器表达式时,你不会有内存损失。以对一些数字进行平方为例:
1 | nums_squared_lc = [num**2 for num in range(5)] # list 列表 |
执行时:
1 | nums_squared_lc |
第一个对象使用括号构建列表,而第二个对象使用括号创建生成器表达式。输出确认您已经创建了一个生成器对象并且它不同于列表。
比较列表和生成器各自生成对象的大小:
1 | import sys |
可以看出,生成器对象比列表对象小。
比较列表和生成器各自求值的速度快慢:
1 | import cProfile |
可以看出,列表求值比生成器求值快。
yield
How to Use Generators and yield in Python
runoob Python yield 使用浅析
调用生成器函数或使用生成器表达式时,会返回一个称为生成器的特殊迭代器。可以将此生成器分配给一个变量以便使用它。当在生成器上调用特殊方法时,例如next(),函数内的代码将执行到yield。
当命中 Python yield 语句时,程序暂停函数执行并将产生的值返回给调用者。(相反,return完全停止函数执行。)当一个函数被挂起时,该函数的状态被保存。这包括生成器本地的任何变量绑定、指令指针、内部堆栈和任何异常处理。
这允许在调用生成器的方法之一时恢复函数执行。这样,所有函数评估都会在 之后立即恢复yield。可以通过使用多个 Python yield 语句来查看实际效果:
1 | def multi_yield(): |
仔细看看最后一次调用next(). 您可以看到执行已因回溯而爆炸。这是因为生成器和所有迭代器一样,可能会被耗尽。除非你的生成器是无限的,否则你只能迭代一次。一旦所有值都被评估,迭代将停止并且for循环将退出。如果您使用next(),那么您将得到一个显式StopIteration异常。
注意: StopIteration是一个自然异常,它被引发以表示迭代器结束。for例如,循环是围绕StopIteration. for您甚至可以使用循环来实现自己的while循环:
1 | letters = ["a", "b", "c", "y"] |
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
举例:
1 | def fab(max): |
使用高级生成器方法
除了yield,生成器对象还可以使用以下方法:
.send().throw().close()