首页 > 编程语言 >使用 Python 的 yield 创建生成器函数

使用 Python 的 yield 创建生成器函数

时间:2025-01-03 20:13:21浏览次数:1  
标签:函数 迭代 Python 生成器 yield letter print output

 

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

相关文章

  • 【语法】生成器
    python中的推导式、生成器_python生成器推导式-CSDN博客“”“生成器应用的场景是在大数据的范围中使用,切记不可直接用for遍历所有,可能无法短时间内获取所有数据”“”使用yield来实现生成器,并使用next进行激活1,__next__()&next()的区别__next__()是生成器对象......
  • 基于降噪自编码器的时间序列降噪方法-以心电信号为例(Python)
    #Importneededmodulesimportmatplotlibimportmatplotlib.pyplotaspltimportnumpyasnpimportpandasaspdimporttensorflowastffromscipy.fftimportfft,fftfreqfromscipy.signalimportbutter,lfilter,freqz,bode,filtfiltfromsklearn.mo......
  • Python语法——增加代码可读性
    类型注释增加代码可读性fromtypingimportList,Dict,Set,Union,Optionaldefadd_enter(b:str)->str:returnb+'\n'defparse_data(data):total=0fork,vsindata.items():ifk[1]:forvinvs:......
  • python毕设 网上商城购物系统程序+论文
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容一、选题背景关于网上商城购物系统的研究,现有研究主要集中在系统的整体架构、用户体验优化等方面,多以大型电商平台为研究对象。专门针对使用Python......
  • 淘宝店铺商品数据洞察:利用Python爬虫获取item_search_shop接口
    引言在电子商务的世界里,商品详情页是连接商家与消费者的重要桥梁。它不仅展示了商品的详细信息,还直接影响着消费者的购买决策。淘宝作为全球知名的电商平台,提供了丰富的API接口,使得开发者能够获取商品的详细信息。本文将探讨如何利用JAVA爬虫技术,获取淘宝的item_get_pro接口,以......
  • WxPython跨平台开发框架之模块字段权限的管理
    在我的很多Winform开发项目中,统一采用了权限管理模块来进行各种权限的控制,包括常规的功能权限(工具栏、按钮、菜单权限),另外还可以进行字段级别的字段权限控制,字段权限是我们在一些对权限要求比较严格的系统里面涉及到的,可以对部分用户隐藏一些敏感的信息,或者禁止不够权限的用户编辑......
  • 淘宝店铺商品数据洞察:利用Python爬虫获取item_search_shop接口
    引言在电商领域,数据的力量不容小觑。对于淘宝店铺而言,掌握店铺内所有商品的数据,对于优化库存、提升销售策略、增强用户体验等方面都至关重要。本文将探讨如何利用Python爬虫技术,获取淘宝的item_search_shop接口,以获得店铺的所有商品信息,包括商品ID、名称、价格、库存量等关键数据......
  • 新年到了!使用Python创建一个简易的接金元宝游戏
    引言在本教程中,我们将一起学习如何使用Python编程语言和Pygame库来创建一个简单的休闲游戏——“接金元宝”。准备工作 首先,确保你的计算机上已经安装了Python(推荐3.6以上版本)和Pygame库。如果还没有安装Pygame,可以通过pip命令轻松安装:pipinstallpygame没有安装的可......
  • python中的优先队列
    在Python中,优先队列(PriorityQueue)是一个可以随时获取队列中最大(或最小)元素的数据结构。Python的标准库heapq提供了一个实现最小堆的优先队列,默认情况下是最小堆,但可以通过一些技巧来实现最大堆。优先队列在算法中常用于求解最短路径、合并有序链表、求解k个最小/最大的元......
  • [oeasy]python056_python中下划线是什么意思_underscore_理解_声明与赋值_改名字
    python中下划线是什么意思_underscore_理解_声明与赋值_改名字回忆上次内容上次了解到已有的函数名、类名、模块名不适合覆盖了赋新值会失去原有功能比如max   添加图片注释,不超过140字(可选) 如果我就想让max当......