Python 中的 yield 关键字将常规函数转换为生成器,它可以按需生成一系列值,而不是一次性计算所有值。
Python 函数并不总是有返回语句。生成器函数是用 yield 关键字代替 return 的函数。这些函数产生生成器迭代器,它是表示数据流的对象。迭代器所代表的元素只有在需要时才会被创建和产生。这种类型的评估通常被称为懒评估。
在处理大型数据集时,生成器提供了一种节省内存的替代方法,而不是将数据存储在列表、元组和其他数据结构中,因为这些数据结构中的每个元素都需要占用内存空间。生成器函数还可以创建无限迭代器,而急于求值的列表和元组等结构则无法做到这一点。
在开始之前,我们先来回顾一下函数和生成器之间的区别:
特征 |
函数 |
生成器 |
生成的值 |
一次返回所有的值 |
根据需要,每次yeild一个值 |
执行 |
在返回值之前全部执行完成 |
每次yeild之后都会暂停,请求下一个值的时候再恢复 |
关键字 |
return |
yield |
内存使用 |
所有结果存在内存中,内存占用会很高 |
只是存储当前的值和下一次的状态,内存占用会很低 |
迭代 |
可能有多个迭代,但是要存储所有的结果 |
单次迭代,对于大的或无限序列很有用 |
使用 Python 的 yield 创建生成器函数
Python 中的生成器一词可以指生成器迭代器或生成器函数。它们是 Python 中不同但相关的对象。
先来探讨一下生成器函数。生成器函数与普通函数相似,但包含 yield 关键字,而不是 return。
当 Python 程序调用一个生成器函数时,它会创建一个生成器迭代器。迭代器按需产生一个值,并暂停执行,直到需要另一个值。让我们看一个例子来解释这个概念,并演示常规函数和生成器函数的区别。
使用常规函数
首先,定义一个包含return语句的常规函数。该函数接受一个单词序列和一个字母,并返回一个包含每个单词中字母出现次数的列表:
def find_letter_occurrences(words, letter): output = [] for word in words: output.append(word.count(letter)) return output print( find_letter_occurrences(["apple", "banana", "cherry"], "a") ) #运行结果 [1, 3, 0]
该函数输出了一个包含 1、3 和 0 的 list,因为 apple 中有一个 a,banana 中出现了三个 a,而 cherry 中没有。同样的函数可以重构为使用列表表达式,而不是初始化一个空列表并使用 .append():
def find_letter_occurrences(words, letter): return [word.count(letter) for word in words]
只要调用这个常规函数,它就会返回一个包含所有结果的列表。但是,如果单词列表很大,调用这个常规函数就会对内存提出要求,因为程序会创建并存储一个与原始列表大小相同的新列表。如果该函数在多个输入参数上重复使用,或类似函数在原始数据上执行其他操作,内存压力就会迅速增加。
使用生成器函数
可以使用生成器函数来代替:
def find_letter_occurrences(words, letter): for word in words: yield word.count(letter) words = ["apple", "banana", "cherry"] letter = "a" output = find_letter_occurrences(words, letter) print(output) ##运行结果 <generator object find_letter_occurrences at 0x102935e00>
该函数使用 yield 关键字代替 return。该生成器函数在调用时返回一个生成器对象,并将其赋值给output。该对象是一个迭代器。它不包含代表每个单词中字母出现次数的数据。相反,生成器会在需要时创建并生成这些值。让我们从生成器迭代器中获取第一个值:
print(next(output)) ##输出结果 1
内置函数 next() 是从迭代器中获取下一个值的一种方法。
生成器函数中的代码会一直执行,直到程序到达带有 yield 关键字的那一行。
如果以output为参数再次调用内置的 next(),生成器将从暂停的位置继续执行:
print(next(output)) ##输出结果 3
执行到这里,生成器再次暂停。
第三次调用 next() 时将继续执行:
print(next(output)) ##输出结果 0
代码再次执行到 yield 行,这次的结果是整数 0,因为 cherry 中没有出现 a。
生成器再次暂停。只有当我们第四次调用 next() 时,程序才会决定生成器的命运:
print(next(output)) ##输出结果 Traceback (most recent call last): ... StopIteration
由于列表单词中已没有元素,循环的迭代已经结束。生成器会引发 StopIteration 异常。
在大多数情况下,生成器元素不会直接使用 next() 访问,而是通过另一个迭代过程访问。StopIteration 异常标志着迭代过程的结束。
当生成器迭代器的操作可以用一个表达式表示时,Python 还有另一种创建生成器迭代器的方法,就像前面的例子一样。生成器迭代器的输出可以使用 generator 表达式来创建:
words = ["apple", "banana", "cherry"] letter = "a" output = (word.count(letter) for word in words) print(next(output)) print(next(output)) print(next(output)) print(next(output)) ##执行结果 <generator object <genexpr> at 0x00000249AC834AC0> 1 3 0 Traceback (most recent call last): ... StopIteration
赋值给output的括号中的表达式是一个生成器表达式,它创建的生成器迭代器与生成器函数 find_letter_occurrences() 生成的迭代器类似。
最后,以另一个生成器函数为例,重点说明每次需要元素时,执行是如何暂停和恢复的:
def show_status(): print("Start") yield print("Middle") yield print("End") yield status = show_status() next(status) ##运行结果 <generator object show_status at 0x0000018D410D4AC0> Start
这个生成器函数没有循环。相反,它包含三行带有 yield 关键字的代码。代码在调用生成器函数 show_status() 时创建了一个生成器迭代器 status。程序第一次调用 next(status) 时,生成器开始执行。它会打印字符串 “Start”,并在第一个 yield 表达式后暂停。由于 yield 关键字后没有对象,因此生成器输出 None。
只有在第二次调用 next() 时,程序才会打印字符串 “Middle”:
next(status) ##运行结果 Middle
生成器在第二个 yield 表达式后暂停。第三次调用 next() 会打印出最终字符串 “End”:
next(status) ##运行结果 End
生成器在最后一个 yield 表达式时暂停。下一次程序从生成器迭代器中请求值时,它将引发 StopIteration 异常:
next(status) ##运行结果 Traceback (most recent call last): ... StopIteration
使用生成器迭代器
生成器函数创建生成器迭代器,而迭代器是可迭代的。程序每次调用生成器函数,都会创建一个迭代器。由于迭代器是可迭代的,因此可以在 for 循环和其他迭代过程中使用。
因此,next() 内置函数并不是访问迭代器中元素的唯一方法。本节将探讨使用迭代器的其他方法。
在生成器迭代器中使用 Python 的迭代协议
重温一下前面的生成器函数:
def find_letter_occurrences(words, letter): for word in words: yield word.count(letter) words = ["apple", "banana", "cherry"] letter = "a" output = find_letter_occurrences(words, letter) for value in output: print(value) ##运行结果 1 3 0
这个版本的代码不再多次使用 next(),而是在 for 循环中使用生成器迭代器输出。由于迭代器是可迭代的,因此可以在 for 循环中使用。循环从生成器迭代器中获取项目,直到没有任何值为止。
与列表和元组等数据结构不同,迭代器只能使用一次。如果我们第二次尝试运行相同的 for 循环,代码不会再次打印出值:
def find_letter_occurrences(words, letter): for word in words: yield word.count(letter) words = ["apple", "banana", "cherry"] letter = "a" output = find_letter_occurrences(words, letter) print("First attempt:") for value in output: print(value) print("Second attempt:") for value in output: print(value) ##运行结果 First attempt: 1 3 0 Second attempt:
迭代器已被第一个 for 循环耗尽,因此不能再产生值。如果在生成器迭代器耗尽后再次需要它,我们必须从生成器函数中创建另一个生成器迭代器。
程序中也有可能同时存在多个生成器迭代器:
def find_letter_occurrences(words, letter): for word in words: yield word.count(letter) words = ["apple", "banana", "cherry"] letter = "a" first_output = find_letter_occurrences(words, letter) second_output = find_letter_occurrences(words, letter) print("First value of first_output:") print(next(first_output)) print("Values of second_output:") for value in second_output: print(value) print("Remaining values of first_output:") for value in first_output: print(value) ##运行结果 First value of first_output: 1 Values of second_output: 1 3 0 Remaining values of first_output: 3 0
生成器函数 find_letter_occurrences() 创建了两个生成器迭代器:first_output 和 second_output。虽然两个迭代器都引用了列表单词中的相同数据,但它们的进程是相互独立的。
本示例使用 next() 从 first_output 中获取第一个值。此时,生成器迭代器产生 1 并暂停。程序在 second_output 中循环。由于这个生成器还没有产生任何值,因此循环会遍历第二个迭代器产生的所有值。最后,另一个 for 循环遍历 first_output。但是,这个迭代器已经在程序的早期产生了第一个值。这个循环会遍历这个迭代器中的其余值。
for 循环并不是唯一可以用来遍历生成器迭代器的:
print(*find_letter_occurrences(words, letter)) print(sorted(find_letter_occurrences(words, letter))) ##运行结果 1 3 0 [0, 1, 3]
在这些示例中,程序直接调用生成器函数来创建和使用生成器迭代器,而不是将其赋值给变量。在第一个示例中,迭代器使用星形符号解包。这一过程依赖于与 for 循环相同的迭代协议。
在第二个示例中,生成器迭代器被传递给内置的 sorted(),它需要一个可迭代参数。生成器是可迭代的,因此,只要 Python 的迭代发生,就可以使用生成器。
标签:函数,迭代,Python,生成器,yield,letter,print,output From: https://www.cnblogs.com/abclife/p/18612060