Python 生成器


2021年8月23日, Learn eTutorial
2091

在本教程中,您将掌握 Python 中生成器的所有知识。您将通过简单的示例探索什么是生成器、如何创建和操作生成器、`yield` 关键字的意义、生成器在 Python 中的作用等等。

Python 中的生成器是什么?

从我们之前的教程中,您已经掌握了在 Python 中创建迭代器的知识,这使您能够理解循环的后端工作原理。`iter()` 方法和 `next` 方法的使用、StopIteration 异常等使得代码更加冗长。此外,对于大型数据集或程序来说,记住事实似乎是适得其反的,在这种情况下,自动性更能受到欢迎,以实现有效的执行。

在 Python 编程中,生成器是一种特殊的函数或表达式,它在迭代生成器对象时一次生成或产生一个结果。使生成器比迭代器更有效的一个重要特性是内存空间的使用。生成器通常操作虚拟序列,不一定需要将整个序列存储在内存中。

如何在 Python 中创建生成器?

在 Python 中,创建生成器的两种酷炫方法是使用

  • 生成器表达式
  • 使用 `yield` 关键字的生成器函数

使用生成器表达式创建生成器

生成器表达式的使用是 Python 中构建生成器的一种方式。这种方法对于列表等小型数据集更方便。生成器表达式遵循列表推导的语法,因此我们可以在使用列表推导的地方使用生成器表达式,而且内存消耗更少。系列或序列是虚拟维护的,无需将整个序列保存在内存中。

为了更清晰地理解生成器表达式的概念,让我们通过下面这个简单而优雅的示例来了解列表和生成器之间的区别。

程序 1

Str_list = ['Yellow','Orange','Red']


#  list comprehension
list = [x for x in Str_list]

# print the type
print(type(list))

# Iterate over items in list and print -1st time
for item in list:
    print(item)

# Iterate over items in list and print -2nd time
for item in list:
    print(item) 

输出:

Yellow
Orange
Red

Yellow
Orange
Red

程序 2


Str_list = ['Yellow','Orange','Red']


# Creating a generator using generator expression
gen = (x for x in Str_list)

# Print the type
print(type(gen))

# Iterate over items in generator object and print -1st time 
for item in gen:
    print(item)

# Iterate over items in generator object and print -2nd time 
for item in gen:
    print(item) 

输出:

Yellow
Orange
Red

从上面的代码片段中,您可以观察到以下几点

  1. 在这两种情况下,我们都使用了相同的列表 `Str_list`,其中包含黄色、橙色和红色等颜色的字符串。
  2. 在程序 1 的第二行,我们创建了一个名为 `list` 的列表对象;而在程序 2 中,我们创建了一个名为 **gen** 的生成器对象。
    • 仔细观察,您可以发现**第一个区别**,即创建列表使用方括号,而创建生成器我们使用圆括号。
  3. 第三,我们尝试检查创建对象的类型并打印它们。因此,我们将获得列表对象的 `` 和生成器对象的 ``。
  4. 第四步,借助 for 循环,我们遍历列表对象和生成器对象的每个项目,并打印相应的输出。两者表面上都产生相同的结果。
    • 第二个主要区别在于列表和生成器在内存中存储项目的方式。列表一次性将所有项目存储在内存中,而生成器则不会一次性存储所有项目,而是按需逐个创建并显示,同时存储项目状态,并在下一次调用时从内存中删除前一个项目。   
    • 为了验证这一点,我们可以使用 `len()` 方法来检查我们创建的列表和生成器的长度。`len(list)` 将给出 3 的结果,因为列表包含 3 个项目;但 `len(gen)` 将抛出类型错误,指出生成器对象没有长度。
  5. 最后,我们尝试迭代并打印列表和生成器。
    • 令人惊讶的是,这导致了**第三个区别**——列表可以进行任意次数的迭代,而生成器只能迭代一次。

使用关键字 `yield` 创建生成器

关键字 `yield` 在构建 Python 生成器函数中起着至关重要的作用。包含 `yield` 关键字的语句称为 `yield` 语句。生成器函数中的 `yield` 语句用于控制函数的执行流程,就像普通函数中的 `return` 语句一样。

为了更清楚地说明,让我们使用 `yield` 创建一个生成器函数来打印颜色列表。

colours = ["Yellow", "Orange", "Red"]

def print_colours(colours):
 for c in colours:
  yield c

colour_generator = print_colours(colours)

for c in colour_generator:
 print(c) 

输出

Yellow
Orange
Red

在上面的代码片段中,我们使用 `yield` 语句创建了一个生成器函数 `print_colours`。`colour_generator` 是生成器函数返回的特殊迭代器(名为生成器)的对象。要打印生成器中的元素,我们必须使用循环或 `next()` 函数,这里我们使用“for 循环”,因此一次性列出了生成器中的所有值。

生成器函数与普通函数有何不同

在讨论区别之前,请检查下面的代码以打印颜色列表

普通函数

clr_list =["Yellow", "Orange", "Red"]

def print_colours(clrs):
    
    for c in clrs:
        return c

C_List = print_colours(clr_list)

print(C_List) 

输出

['Yellow', 'Orange', 'Red']

生成器函数

clr_list =["Yellow", "Orange", "Red"]
def print_colours(clrs):
    
    for c in clrs:
        yield c

C_gen = print_colours(clr_list)

print(C_gen)
print(next(C_gen))
print(next(C_gen))
print(next(C_gen))
print(next(C_gen)) 

输出:

Yellow
Orange
Red
Traceback (most recent call last):
  File "gen_ex.py", line 62, in 
    print(next(C_gen))
StopIteration

当您观察两个代码片段时,您会看到以下发现。

  • 生成器函数在 `print_colours()` 函数内包含 `yield` 语句,而普通函数在 `print_colours()` 函数内包含 `return` 语句。
  • 函数调用时,生成器由生成器函数返回,而列表由普通函数返回。
  • `return` 和 `yield` 语句都用于控制执行流程,关键区别在于:
    • `return` 语句将通过返回列表中的所有值来终止整个函数。
    • `yield` 语句将仅通过返回 `yield` 的值来暂停函数。
  • C_List 包含整个列表,而 C_gen 包含一个称为生成器的特殊迭代器。可以通过打印它们来验证这一点。
    • 当执行 `print(C_List)` 语句时,我们得到整个列表 `["Yellow", "Orange", "Red"]`;而当执行 `print(C_gen)` 时,我们得到输出 ``。这意味着列表中没有元素存储在内存中,只创建了一个迭代器。
  • 在生成器函数中,我们需要 `next()` 这样的特殊方法来激活函数的执行,而普通函数则不需要。
    • 当遇到 `next()` 函数时,生成器函数 `print_colours` 将开始执行,直到遇到 `yield` 语句。
    • `yield` 语句将返回 `yield` 的值给调用者,同时暂停函数的执行。`yield` 语句将始终保存该函数的状态,因此不会遍历已访问过的元素。
    • 直到 `next()` 函数在列表中没有元素时引发 `StopIteration` 异常,此过程才会重复。
    • 在上面的示例中,您可以看到我们使用了 4 次 `next()` 函数,而列表只包含 3 个元素,因此前三个 `next()` 函数返回 3 个元素(黄色、橙色和红色),第四个 `next()` 函数引发 `StopIteration` 异常。

生成器函数和 for 循环

现在我们已经充分了解了生成器及其工作原理。`next()` 函数的使用并非最佳实践,因为我们需要多次提及它。循环可以覆盖 `next()` 函数,因为它更方便。请看下面的示例以反转列表。

# generator to reverse a list
def reverse_list(clr_list):
    length = len(clr_list)
    for i in range(length-1, -1, -1):
        yield clr_list[i]

# using for loop to reverse the list
for list in reverse_list(["Yellow", "Orange", "Red"]):
    print(list) 

输出

Red
Orange
Yellow

在此脚本中,执行从在 `for` 循环中调用生成器函数 `reverse_list` 开始。生成器函数将借助 `range()` 函数反转给定的列表。`for` 循环中的 `range()` 函数用于以相反的顺序获取列表的索引。因此,我们按相反的顺序 `yield` 列表元素。由于函数调用写在 `for` 循环内部,因此它会执行直到遇到最后一个元素。

注意:Python 生成器可以与所有可迭代对象良好地配合使用。

使用生成器的两个主要优点是

  • 迭代器相比,生成器**更高效,代码更少,易于实现**。
  • 由于生成器使用虚拟空间来存储函数状态,因此可以说,在处理大型数据集时,生成器是**内存效率高**的。