原文: https://www.programiz.com/python-programming/generator

在本教程中,您将学习如何使用 Python 生成器轻松创建迭代,它与迭代器和常规函数的区别以及为什么要使用它。

Python 中的生成器

在 Python 中构建迭代器的工作很多。 我们必须使用__iter__()__next__()方法实现一个类,跟踪内部状态,并在没有值要返回时提高StopIteration

这既冗长又违反直觉。 在这种情况下,发电机将进行救援。

Python 生成器是创建迭代器的简单方法。 我们上面提到的所有工作都由 Python 的生成器自动处理。

简而言之,生成器是一个函数,它返回一个对象(迭代器),我们可以对其进行迭代(一次一个值)。


用 Python 创建生成器

在 Python 中创建生成器非常简单。 它与定义普通函数一样简单,但是使用yield语句而不是return语句。

如果一个函数至少包含一个yield语句(它可能包含其他yieldreturn语句),则它将成为生成器函数。yieldreturn都将从函数返回一些值。

不同之处在于,虽然return语句完全终止了一个函数,但yield语句暂停了该函数并保存了所有状态,然后在后续调用中从那里继续。


生成器函数与常规函数之间的区别

这是生成器功函数与常规函数不同的地方。

  • 生成器函数包含一个或多个yield语句。
  • 调用时,它返回一个对象(迭代器),但不会立即开始执行。
  • __iter__()__next__()之类的方法会自动实现。 因此,我们可以使用next()遍历项目。
  • 一旦函数屈服,函数将暂停并将控件转移到调用方。
  • 连续调用之间会记住局部变量及其状态。
  • 最后,当函数终止时,StopIteration在以后的调用中自动加高。

这是一个示例,用于说明上述所有要点。 我们有一个名为my_gen()的生成器函数,带有几个yield语句。

  1. # A simple generator function
  2. def my_gen():
  3. n = 1
  4. print('This is printed first')
  5. # Generator function contains yield statements
  6. yield n
  7. n += 1
  8. print('This is printed second')
  9. yield n
  10. n += 1
  11. print('This is printed at last')
  12. yield n

解释器中的交互式运行如下所示。 在 Python Shell 中运行这些命令以查看输出。

  1. >>> # It returns an object but does not start execution immediately.
  2. >>> a = my_gen()
  3. >>> # We can iterate through the items using next().
  4. >>> next(a)
  5. This is printed first
  6. 1
  7. >>> # Once the function yields, the function is paused and the control is transferred to the caller.
  8. >>> # Local variables and theirs states are remembered between successive calls.
  9. >>> next(a)
  10. This is printed second
  11. 2
  12. >>> next(a)
  13. This is printed at last
  14. 3
  15. >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
  16. >>> next(a)
  17. Traceback (most recent call last):
  18. ...
  19. StopIteration
  20. >>> next(a)
  21. Traceback (most recent call last):
  22. ...
  23. StopIteration

在上面的示例中要注意的一件有趣的事情是,每次调用之间都会记住变量n的值。

与普通函数不同,函数屈服时不会破坏局部变量。 此外,生成器对象只能被迭代一次。

要重新启动该过程,我们需要使用a = my_gen()之类的东西来创建另一个生成器对象。

最后要注意的一点是,我们可以将生成器直接用于for循环

这是因为for循环使用一个迭代器,并使用next()函数对其进行迭代。 抛出StopIteration时,它自动结束。 检查这里知道在 Python 中实际上是如何实现for循环的。

  1. # A simple generator function
  2. def my_gen():
  3. n = 1
  4. print('This is printed first')
  5. # Generator function contains yield statements
  6. yield n
  7. n += 1
  8. print('This is printed second')
  9. yield n
  10. n += 1
  11. print('This is printed at last')
  12. yield n
  13. # Using for loop
  14. for item in my_gen():
  15. print(item)

运行该程序时,输出为:

  1. This is printed first
  2. 1
  3. This is printed second
  4. 2
  5. This is printed at last
  6. 3

带有循环的 Python 生成器

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

通常,生成器函数通过具有合适终止条件的循环来实现。

让我们以反转字符串的生成器为例。

  1. def rev_str(my_str):
  2. length = len(my_str)
  3. for i in range(length - 1, -1, -1):
  4. yield my_str[i]
  5. # For loop to reverse the string
  6. for char in rev_str("hello"):
  7. print(char)

输出

  1. o
  2. l
  3. l
  4. e
  5. h

在此示例中,我们使用range()函数使用for循环以相反的顺序获取索引。

注意:此生成器函数不仅适用于字符串,还适用于其他种类的可迭代变量,例如列表元组等。


Python 生成器表达式

使用生成器表达式可以轻松地动态创建简单的生成器。 它使建造发电机变得容易。

类似于创建匿名函数的 lambda 函数,生成器表达式创建匿名生成器函数。

生成器表达式的语法类似于 Python 中的列表推导式语法。 但是方括号将替换为圆括号。

列表推导式与生成器表达式之间的主要区别在于,列表生成器生成整个列表,而生成器表达式一次生成一个项。

他们懒惰的执行(仅在需要时才生成项目)。 因此,生成器表达式比等效的列表推导式具有更高的内存效率。

  1. # Initialize the list
  2. my_list = [1, 3, 6, 10]
  3. # square each term using list comprehension
  4. list_ = [x**2 for x in my_list]
  5. # same thing can be done using a generator expression
  6. # generator expressions are surrounded by parenthesis ()
  7. generator = (x**2 for x in my_list)
  8. print(list_)
  9. print(generator)

输出

  1. [1, 9, 36, 100]
  2. <generator object <genexpr> at 0x7f5d4eb4bf50>

上面我们可以看到生成器表达式没有立即产生所需的结果。 相反,它返回一个生成器对象,该对象仅按需生成项目。

这是我们如何开始从生成器获取项目的方法:

  1. # Initialize the list
  2. my_list = [1, 3, 6, 10]
  3. a = (x**2 for x in my_list)
  4. print(next(a))
  5. print(next(a))
  6. print(next(a))
  7. print(next(a))
  8. next(a)

当我们运行上面的程序时,我们得到以下输出:

  1. 1
  2. 9
  3. 36
  4. 100
  5. Traceback (most recent call last):
  6. File "<string>", line 15, in <module>
  7. StopIteration

生成器表达式可以用作函数参数。 以这种方式使用时,可以删除圆括号。

  1. >>> sum(x**2 for x in my_list)
  2. 146
  3. >>> max(x**2 for x in my_list)
  4. 100

使用 Python 生成器

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

1.易于实现

与生成器类的生成器相比,生成器可以以简洁明了的方式实现。 下面是一个使用迭代器类实现 2 的幂序列的示例。

  1. class PowTwo:
  2. def __init__(self, max=0):
  3. self.n = 0
  4. self.max = max
  5. def __iter__(self):
  6. return self
  7. def __next__(self):
  8. if self.n > self.max:
  9. raise StopIteration
  10. result = 2 ** self.n
  11. self.n += 1
  12. return result

上面的程序冗长而令人困惑。 现在,让我们使用生成器函数执行相同的操作。

  1. def PowTwoGen(max=0):
  2. n = 0
  3. while n < max:
  4. yield 2 ** n
  5. n += 1

由于生成器自动跟踪细节,因此实现简洁明了。

2.内存有效

一个普通的返回序列的函数会在返回结果之前在内存中创建整个序列。 如果序列中的项目数量很大,这是一个过大的杀伤力。

这种序列的生成器实现是内存友好的,因此是首选的,因为它一次只能生成一项。

3.表示无限流

生成器是代表无限数据流的绝佳媒介。 无限流不能存储在内存中,并且由于生成器一次只生成一项,因此它们可以表示无限数据流。

以下生成器函数可以生成所有偶数(至少在理论上)。

  1. def all_even():
  2. n = 0
  3. while True:
  4. yield n
  5. n += 2

4.像流水线一样连接生成器

可以使用多个生成器来流水线化一系列操作。 最好用一个例子来说明。

假设我们有一个生成斐波那契数列中数字的生成器。 我们还有另一个用于平方的生成器。

如果我们想找出斐波那契数列中数字的平方和,可以通过以下方式将生成器函数的输出流水线化来实现。

  1. def fibonacci_numbers(nums):
  2. x, y = 0, 1
  3. for _ in range(nums):
  4. x, y = y, x+y
  5. yield x
  6. def square(nums):
  7. for num in nums:
  8. yield num**2
  9. print(sum(square(fibonacci_numbers(10))))

输出

  1. 4895

这种流水线高效且易于阅读(是的,非常酷!)。