Python 中的生成器

在 Python中构建迭代器有很多工作要做。我们必须用iter()和next()方法实现一个类,跟踪内部状态,并StopIteration在没有要返回的值时引发。
这既冗长又违反直觉。在这种情况下,发电机会派上用场。
Python 生成器是一种创建迭代器的简单方法。我们上面提到的所有工作都是由 Python 中的生成器自动处理的。
简单地说,生成器是一个函数,它返回一个我们可以迭代的对象(迭代器)(一次一个值)。


在 Python 中创建生成器

在 Python 中创建生成器相当简单。它就像定义一个普通的函数一样简单,只是用一个yield语句而不是一个return语句。
如果一个函数包含至少一个yield语句(它可能包含其他yield或多个return语句),它就成为一个生成器函数。双方yield并return会从一个函数返回一定的价值。
不同之处在于,当一个return语句完全终止一个函数时,yield语句暂停该函数以保存其所有状态,然后在连续调用时从那里继续。


Generator 函数与 Normal 函数的区别

以下是生成器函数与普通函数的不同之处。

  • 生成器函数包含一个或多个yield语句。
  • 调用时,它返回一个对象(迭代器)但不会立即开始执行。
  • iter()和next()这样的方法是自动实现的。所以我们可以使用next().
  • 一旦函数让出,函数就会暂停,控制权转移给调用者。
  • 局部变量及其状态在连续调用之间被记住。
  • 最后,当函数终止时,StopIteration会在进一步调用时自动引发。

下面是一个示例来说明上述所有要点。我们有一个my_gen()用几个yield语句命名的生成器函数。
# A simple generator function def my_gen(): n = 1 print(‘This is printed first’) # Generator function contains yield statements yield n n += 1 print(‘This is printed second’) yield n n += 1 print(‘This is printed at last’) yield n
下面给出了解释器中的交互式运行。在 Python shell 中运行这些以查看输出。
>>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last): … StopIteration >>> next(a) Traceback (most recent call last): … StopIteration
在上面的例子中需要注意的一件有趣的事情是变量的值 n 每次调用之间都会记住。
与普通函数不同,局部变量在函数 yield 时不会被销毁。此外,生成器对象只能迭代一次。
要重新启动该过程,我们需要使用类似的东西创建另一个生成器对象a = my_gen()。
最后要注意的一件事是我们可以直接使用带有for 循环的生成器。
这是因为for循环需要一个迭代器并使用next()函数对其进行迭代。StopIteration升起时自动结束。查看此处了解 for 循环是如何在 Python 中实际实现的

A simple generator function def my_gen(): n = 1 print(‘This is printed first’) # Generator function contains yield statements yield n n += 1 print(‘This is printed second’) yield n n += 1 print(‘This is printed at last’) yield n # Using for loop for item in my_gen(): print(item)
当你运行程序时,输出将是:
这是先打印的 1 这是第二次打印 2 这是最后打印出来的 3


带循环的 Python 生成器

上面的例子用处不大,我们研究它只是为了了解背景中发生的事情。

通常,生成器函数是通过具有合适终止条件的循环来实现的。
让我们举一个反转字符串的生成器的例子。

def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str[i] # For loop to reverse the string for char in rev_str(“hello”): print(char)
输出
○ 升 升 电子 H
在此示例中,我们使用该range()函数通过 for 循环以相反的顺序获取索引。
注意:这个生成器函数不仅适用于字符串,还适用于其他类型的可迭代对象,如listtuple等。


Python 生成器表达式

使用生成器表达式可以轻松地动态创建简单的生成器。它使构建生成器变得容易。
类似于创建匿名函数的 lambda 函数,生成器表达式创建匿名生成器函数。
生成器表达式的语法类似于Python中的列表推导式。但是方括号被圆括号代替。
列表推导式和生成器表达式之间的主要区别在于,列表推导式生成整个列表,而生成器表达式一次生成一项。
他们有延迟执行(仅在需要时才生成项目)。出于这个原因,生成器表达式的内存效率比等效的列表推导式高得多。

Initialize the list mylist = [1, 3, 6, 10] # square each term using list comprehension list = [x2 for x in my_list] # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x2 for x in mylist) print(list) print(generator)
输出
[1, 9, 36, 100] <生成器对象 在 0x7f5d4eb4bf50>
我们可以在上面看到生成器表达式并没有立即产生所需的结果。相反,它返回一个生成器对象,该对象仅根据需要生成项目。
以下是我们如何开始从生成器获取项目:

Initialize the list my_list = [1, 3, 6, 10] a = (x2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)
当我们运行上面的程序时,我们得到以下输出:
1 9 36 100 回溯(最近一次调用最后一次): 文件“”,第 15 行,在 中 停止迭代
生成器表达式可以用作函数参数。以这种方式使用时,可以去掉圆括号。
>>> sum(x
2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100


Python 生成器的使用

有几个原因使生成器成为一个强大的实现。

1. 易于实施

与迭代器类对应物相比,生成器可以以清晰简洁的方式实现。以下是使用迭代器类实现 2 的幂序列的示例。
class PowTwo: def init(self, max=0): self.n = 0 self.max = max def iter(self): return self def next(self): if self.n > self.max: raise StopIteration result = 2 self.n self.n += 1 return result
上面的程序冗长而混乱。现在,让我们使用生成器函数来做同样的事情。
def PowTwoGen(max=0): n = 0 while n < max: yield 2
n n += 1
由于生成器会自动跟踪细节,因此实现更加简洁明了。

2. 内存效率高

返回序列的普通函数将在返回结果之前在内存中创建整个序列。如果序列中的项目数量非常大,这是一种矫枉过正的做法。
这种序列的生成器实现是内存友好的并且是首选的,因为它一次只生成一个项目。

3. 表示无限流

生成器是表示无限数据流的绝佳媒介。无限流不能存储在内存中,并且由于生成器一次只生成一项,因此它们可以表示无限数据流。
下面的生成器函数可以生成所有的偶数(至少理论上是这样)。
def all_even(): n = 0 while True: yield n n += 2

4. 流水线生成器

多个生成器可用于流水线化一系列操作。最好使用示例来说明这一点。
假设我们有一个生成器生成斐波那契数列中的数字。我们还有另一个用于平方数的生成器。
如果我们想找出斐波那契数列中数字的平方和,我们可以通过以下方式将生成器函数的输出流水线化在一起。

def fibonaccinumbers(nums): x, y = 0, 1 for in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num2 print(sum(square(fibonacci_numbers(10))))
输出**
4895
这种流水线高效且易于阅读(是的,更酷!)。