Python 迭代器和生成器:深入理解与应用

目录

一、引言

二、迭代器(Iterator)

(一)迭代器的概念

(二)迭代器的创建

(三)迭代器的使用

(四)迭代器的优点

(五)迭代器的局限性

三、生成器(Generator)

(一)生成器的概念

(二)生成器函数

(三)生成器表达式

(四)生成器的优点

(五)生成器的应用场景

四、迭代器和生成器的关系与区别

(一)关系

(二)区别

五、高级主题

(一)迭代器的迭代器(嵌套迭代器)

(二)生成器的高级特性

(三)异步生成器(Python 3.5 及以上版本)


一、引言

在 Python 编程中,迭代器和生成器是两个非常重要的概念,它们为处理序列数据提供了高效、灵活且优雅的方式。迭代器允许我们逐个访问容器中的元素,而生成器则可以按需生成数据,避免一次性生成大量数据占用过多内存。理解和掌握迭代器与生成器对于编写高效、简洁且易于维护的 Python 代码至关重要。

二、迭代器(Iterator)

(一)迭代器的概念

迭代器是一个表示数据流的对象,它实现了迭代器协议,该协议包含两个方法:__iter__()__next__()。任何包含这两个方法的对象都可以被视为迭代器,用于在遍历容器(如列表、元组、字典等)时提供逐个元素的访问方式,而无需暴露容器的内部结构。

(二)迭代器的创建

  1. 使用内置函数iter()
    • 对于许多内置的可迭代对象(如列表、元组、字符串等),可以使用iter()函数创建迭代器。例如:
my_list = [1, 2, 3, 4, 5]
my_iter = iter(my_list)
  • 这里,my_iter就是my_list的迭代器。
  1. 定义类实现迭代器协议
    • 我们也可以通过定义一个类来创建自定义的迭代器。这个类需要实现__iter__()__next__()方法。例如,下面是一个简单的迭代器类,用于生成从 1 开始的连续整数:
class MyIterator:def __init__(self, max_num):self.current = 1self.max_num = max_numdef __iter__(self):return selfdef __next__(self):if self.current <= self.max_num:result = self.currentself.current += 1return resultelse:raise StopIteration
  • 在这个类中,__iter__()方法返回迭代器对象本身(通常是self),__next__()方法用于逐个返回下一个整数,当达到最大值时,抛出StopIteration异常来表示迭代结束。

(三)迭代器的使用

  1. 使用next()函数遍历
    • 一旦创建了迭代器,可以使用next()函数逐个获取元素。例如:
my_iter = MyIterator(5)
print(next(my_iter))  
print(next(my_iter))  
  • 每次调用next()函数,迭代器就会返回下一个元素,直到没有更多元素时抛出StopIteration异常。
  1. 使用for循环遍历
    • 更常见的是使用for循环来遍历迭代器,for循环会自动处理StopIteration异常,使代码更加简洁和安全。例如:
my_iter = MyIterator(5)
for num in my_iter:print(num)
  • 这样就可以方便地遍历迭代器中的所有元素。

(四)迭代器的优点

  1. 节省内存
    • 迭代器在处理大型数据集时非常高效,因为它不需要一次性将所有元素加载到内存中,而是逐个生成或获取元素。例如,当处理一个非常大的文件中的数据时,如果使用迭代器,可以逐行读取文件内容,而不是一次性将整个文件读入内存。
  2. 支持无限序列
    • 可以创建迭代器来表示无限序列,如斐波那契数列等。由于迭代器是按需生成元素,所以即使序列是无限的,也可以在需要时获取元素,而不会导致内存溢出。
  3. 解耦数据生成和使用
    • 迭代器将数据的生成和使用分离开来,使得代码结构更加清晰。数据生成部分可以专注于生成元素,而使用部分可以简单地遍历迭代器获取元素,两者之间的耦合度较低,便于代码的维护和扩展。

(五)迭代器的局限性

  1. 一次性使用
    • 迭代器一旦被耗尽(即遍历完所有元素),就不能再次使用,除非重新创建迭代器。如果需要多次遍历同一个数据集,可能需要每次都重新创建迭代器,这在某些情况下可能不太方便。
  2. 状态维护
    • 迭代器需要维护自身的状态,以便知道下一个要返回的元素。在多线程环境中,如果多个线程同时访问同一个迭代器,可能会导致状态混乱,需要额外的同步机制来确保正确性。

三、生成器(Generator)

(一)生成器的概念

生成器是一种特殊的迭代器,它使用更简洁的语法来创建迭代器。生成器函数(使用yield语句的函数)或生成器表达式可以用来创建生成器对象,它能够在需要时生成数据,而不是一次性生成所有数据并存储在内存中。

(二)生成器函数

  1. 定义和语法
    • 生成器函数是一种包含yield语句的函数。当调用生成器函数时,它返回一个生成器对象,而不是立即执行函数体中的代码。例如,下面是一个简单的生成器函数,用于生成从 1 到n的整数:
def my_generator(n):i = 1while i <= n:yield ii += 1
  • 在这个函数中,yield语句起到暂停函数执行返回当前值的作用,同时记住函数的执行状态,下次调用生成器的__next__()方法(隐式地,如在for循环中)时,函数会从上次暂停的地方继续执行。
  1. 生成器函数的执行过程
    • 当第一次调用生成器函数返回的生成器对象的__next__()方法(或在for循环中使用)时,函数开始执行,直到遇到yield语句,此时返回yield后面的值,并暂停函数执行。下次调用__next__()方法时,函数从上次暂停的位置继续执行,直到再次遇到yield语句或函数结束(如果函数结束,则抛出StopIteration异常)。例如:
gen = my_generator(5)
print(next(gen))  
print(next(gen))  
  • 第一次调用next(gen)时,函数执行到yield i,返回 1 并暂停。第二次调用时,从i += 1继续执行,然后再次遇到yield i,返回 2 并暂停,以此类推。

(三)生成器表达式

  1. 语法和示例
    • 生成器表达式是一种简洁的创建生成器的方式,类似于列表推导式,但使用圆括号而不是方括号。例如,下面的生成器表达式可以生成 1 到 10 的平方数:
gen_expr = (i**2 for i in range(1, 11))
  • 它类似于[i**2 for i in range(1, 11)],但后者是创建一个列表,会一次性计算并存储所有元素,而生成器表达式是按需生成元素。
  1. 与生成器函数的比较
    • 生成器表达式通常用于简单的生成器场景,代码简洁。而生成器函数更适合复杂的生成逻辑,因为可以包含更多的语句和逻辑控制。生成器函数可以有多个yield语句,甚至可以包含其他函数调用等复杂逻辑,而生成器表达式相对更简单直接。

(四)生成器的优点

  1. 简洁高效的内存使用
    • 与迭代器类似,生成器也是按需生成数据,避免了一次性生成大量数据占用过多内存。对于大型数据集或无限序列,生成器可以有效地处理,只在需要时生成和返回元素,提高了内存使用效率。
  2. 易于实现和阅读
    • 生成器函数和生成器表达式的语法相对简洁,使得生成器的创建和使用更加直观。相比于定义一个完整的迭代器类,使用生成器可以用更少的代码实现相同的功能,提高了代码的可读性和可维护性。
  3. 支持协同程序(Coroutine)特性(在 Python 3.5 及以上版本中更明显)
    • 生成器可以作为协程使用,通过yieldsend()等方法实现生产者 - 消费者模式等异步编程模式,使得在处理异步任务时更加方便和高效。例如,可以在生成器函数中使用yield暂停执行,等待外部数据(通过send()方法发送),然后根据接收到的数据继续执行,实现了一种简单的异步交互机制。

(五)生成器的应用场景

  1. 处理大型文件
    • 当需要处理大型文件中的数据时,使用生成器可以逐行读取文件内容,而不是一次性将整个文件读入内存。例如:
def read_large_file(file_path):with open(file_path, 'r') as file:for line in file:yield line.strip()
  • 这样可以在处理文件时节省大量内存,特别是对于无法一次性加载到内存中的超大文件。
  1. 生成无限序列
    • 如生成斐波那契数列、素数序列等无限序列时,生成器非常有用。例如:
def fibonacci():a, b = 0, 1while True:yield aa, b = b, a + b
  • 可以通过不断调用生成器的__next__()方法(或在for循环中使用)来获取无限的斐波那契数列元素,而不会导致内存溢出。
  1. 数据管道和流式处理
    • 在数据处理管道中,生成器可以作为各个处理阶段之间的数据传递方式。每个阶段可以是一个生成器函数,对上一阶段生成的数据进行处理并生成新的数据,形成一个流式处理的管道。例如:
def data_source():# 生成原始数据for i in range(100):yield idef process_data(data):# 对数据进行处理for item in data:yield item * 2def consume_data(data):# 消费处理后的数据for item in data:print(item)data = data_source()
processed_data = process_data(data)
consume_data(processed_data)
  • 这样可以将数据处理过程分解为多个简单的阶段,每个阶段只关注自己的处理逻辑,并且通过生成器实现高效的数据传递和处理。

四、迭代器和生成器的关系与区别

(一)关系

  1. 生成器是特殊的迭代器
    • 生成器自动实现了迭代器协议,它包含了__iter__()__next__()方法(通过生成器对象的创建和yield语句的机制隐式实现),因此可以像迭代器一样使用,用于遍历数据。
  2. 都支持迭代协议
    • 迭代器和生成器都可以在for循环等需要迭代的场景中使用,它们提供了一种统一的方式来处理序列数据,使得代码可以不依赖于具体的数据结构(如列表、元组等),而是通过迭代的方式访问数据。

(二)区别

  1. 创建方式
    • 迭代器可以通过定义类实现__iter__()__next__()方法来创建,也可以使用iter()函数从可迭代对象创建。而生成器主要通过生成器函数(使用yield语句)或生成器表达式来创建,创建方式更加简洁和灵活。
  2. 内存使用和数据生成时机
    • 虽然两者都能节省内存,但生成器在这方面更加灵活和高效。迭代器在创建时可能已经确定了数据的范围(如通过__init__()方法初始化数据),而生成器是在每次调用__next__()方法(或在for循环中隐式调用)时按需生成数据,更加动态和节省内存,尤其适用于处理无限序列或非常大的数据集合。
  3. 代码复杂性和功能扩展性
    • 迭代器类的定义相对复杂,需要完整地实现迭代器协议,并且在需要扩展功能时可能需要修改类的定义。生成器函数则更加简洁,易于编写和理解,并且可以通过在函数中添加更多逻辑(如条件判断、其他函数调用等)来扩展功能,同时保持生成器的特性。例如,如果要在生成器中添加对数据的过滤功能,只需要在生成器函数中添加相应的条件判断语句即可,而对于迭代器类可能需要修改__next__()方法的实现。

五、高级主题

(一)迭代器的迭代器(嵌套迭代器)

  1. 概念和示例
    • 在 Python 中,可以创建迭代器的迭代器,即嵌套迭代器。例如,假设有一个二维列表,我们可以创建一个迭代器来遍历这个二维列表中的每个子列表,然后再为每个子列表创建一个迭代器来遍历其中的元素。以下是一个简单的示例:
my_2d_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
outer_iter = iter(my_2d_list)
while True:try:inner_list = next(outer_iter)inner_iter = iter(inner_list)while True:try:print(next(inner_iter))except StopIteration:breakexcept StopIteration:break
  • 这里,首先创建了一个外层迭代器outer_iter来遍历二维列表中的子列表,然后对于每个子列表,又创建了一个内层迭代器inner_iter来遍历子列表中的元素
  1. 应用场景和注意事项
    • 嵌套迭代器在处理多维数据结构或需要对数据进行分层遍历的场景中非常有用,比如处理矩阵、嵌套的字典等数据结构。然而,使用嵌套迭代器时需要注意代码的可读性,过多的嵌套可能会使代码变得复杂难以理解。同时,在处理大型多维数据时,也要注意内存使用情况,虽然迭代器本身可以节省内存,但过多的嵌套和复杂的数据结构可能仍然会导致内存问题。

(二)生成器的高级特性

  1. yield from语句(Python 3.3 及以上版本)
    • yield from语句可以简化生成器函数中对另一个可迭代对象的迭代。例如,如果有一个生成器函数需要生成另一个生成器或可迭代对象中的所有元素,可以使用yield from来代替显式的循环。以下是一个示例:
def sub_generator():yield 1yield 2yield 3def main_generator():yield from sub_generator()yield 4yield 5for num in main_generator():print(num)
  • main_generator函数中,yield from sub_generator()语句会自动迭代sub_generator生成的所有元素,然后继续执行main_generator中的后续yield语句。yield from语句不仅使代码更加简洁,还提高了生成器的性能,因为它避免了额外的迭代开销。
  1. 生成器的关闭和异常处理
    • 生成器可以通过close()方法显式关闭,这在需要提前终止生成器的执行时非常有用。例如,如果在生成器执行过程中发生了某些条件,不再需要生成器继续生成数据,可以调用close()方法来释放资源并停止生成器。同时,生成器在执行过程中可以捕获和处理异常。例如,如果在生成器函数中执行yield语句时发生异常,可以在生成器函数中使用try - except块来捕获和处理异常,以确保生成器的正常运行或进行适当的错误处理。以下是一个简单的示例:
def my_generator_with_exception():try:yield 1raise ValueError("An error occurred")yield 2except ValueError as e:print(f"Caught exception: {e}")gen = my_generator_with_exception()
print(next(gen))  
try:next(gen)
except StopIteration:print("Generator stopped due to exception")
  • 在这个示例中,生成器在生成第一个元素后抛出了一个ValueError异常,在生成器函数内部捕获并打印了异常信息,然后在外部调用next(gen)时,由于异常导致生成器停止,捕获了StopIteration异常并打印了相应信息。

(三)异步生成器(Python 3.5 及以上版本)

  1. 概念和语法
    • 异步生成器是 Python 异步编程中的一个重要特性,它允许在异步代码中使用生成器的方式生成数据。异步生成器函数使用async def定义,并且在yield语句前加上await关键字。例如:
async def async_generator():for i in range(5):await asyncio.sleep(1)yield i
  • 这里,async_generator是一个异步生成器函数,它会在每次yield之前暂停执行一段时间(通过await asyncio.sleep(1)模拟异步操作),然后继续生成下一个元素。
  1. 在异步编程中的应用场景
    • 异步生成器在处理异步数据流时非常有用,比如在异步网络编程中,当从网络接收数据时,可能不是一次性接收所有数据,而是分批接收。异步生成器可以在数据到达时逐个生成,使得异步处理代码更加简洁和高效。例如,在一个异步的 Web 爬虫中,可以使用异步生成器来生成要爬取的 URL,然后异步地获取每个 URL 对应的页面内容,而无需等待所有 URL 都准备好才开始爬取,提高了爬取效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/492549.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenEuler Linux上怎么测试Nvidia显卡安装情况

当安装好显卡驱动后怎么样知道驱动程序安装好了,这里以T400 OpenEuler 正常情况下,我们只要看一下nvidia-smi 状态就可以确定他已经正常了 如图: 这里就已经确定是可以正常使用了,这里只是没有运行对应的程序,那接来下我们就写一个测试程序来测试一下:以下代码通过AI给出然后…

shell5

字符串运算符 首先我们在终端利用vim打开u.sh str1"hello" str2"hello" if [ "$str1" "$str2" ]; thenecho True elseecho false fi我们把hello改为Hello&#xff0c;看一下大小写是否敏感 str1"Hello" str2"hello…

Linux-ubuntu之主频和时钟配置

Linux-ubuntu之主频和时钟配置 一&#xff0c;主频二&#xff0c;其它时钟配置1.PLL2和PLL3的PFD0-3设置2.AHB_CLK_ROOT3.IPG 和 PERCLK时钟 三&#xff0c;总结 一&#xff0c;主频 24MHz 晶振为内核和其它外设提供时钟源&#xff0c;经电路后到PLL1变为996MHZ&#xff0c;再…

深入浅出Flink CEP丨如何通过Flink SQL作业动态更新Flink CEP作业

复杂事件处理&#xff08;CEP&#xff09;是一种对事件流进行分析的技术&#xff0c;它能够识别出数据流中的事件序列是否符合特定的模式&#xff0c;并允许用户对这些模式进行处理。Flink CEP 是 CEP 在 Apache Flink 中的具体实现&#xff0c;是 Apache Flink 的一个库&#…

华为数通最新题库 H12-821 HCIP稳定过人中

以下是成绩单和考试人员 HCIP H12-831 HCIP H12-725 安全中级

Facebook 与数字社交的未来走向

随着数字技术的飞速发展&#xff0c;社交平台的角色和形式也在不断演变。作为全球最大社交平台之一&#xff0c;Facebook&#xff08;现Meta&#xff09;在推动数字社交的进程中扮演了至关重要的角色。然而&#xff0c;随着互联网的去中心化趋势和新技术的崛起&#xff0c;Face…

STM32中ADC模数转换器

一、ADC简介 ADC模拟-数字转换器 ADC可以将引脚连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 12位逐次逼近型ADC&#xff0c;1us转换时间 输入电压范围&#xff1a; 0~3.3V&#xff0c;转换结果范围&#xff1a;0~4095 18个输入…

fpga系列 HDL:Quartus II PLL (Phase-Locked Loop) IP核 (Quartus II 18.0)

在 Quartus II 中使用 PLL (Phase-Locked Loop) 模块来将输入时钟分频或倍频&#xff0c;并生成多个相位偏移或频率不同的时钟信号&#xff1a; 1. 生成 PLL 模块 在 Quartus II 中&#xff1a; 打开 IP Components。 file:///C:/intelFPGA_lite/18.0/quartus/common/help/w…

Springboot3.x配置类(Configuration)和单元测试

配置类在Spring Boot框架中扮演着关键角色&#xff0c;它使开发者能够利用Java代码定义Bean、设定属性及调整其他Spring相关设置&#xff0c;取代了早期版本中依赖的XML配置文件。 集中化管理&#xff1a;借助Configuration注解&#xff0c;Spring Boot让用户能在一个或几个配…

【游戏中orika完成一个Entity的复制及其Entity异步落地的实现】 1.ctrl+shift+a是飞书下的截图 2.落地实现

一、orika工具使用 1)工具类 package com.xinyue.game.utils;import ma.glasnost.orika.MapperFactory; import ma.glasnost.orika.impl.DefaultMapperFactory;/*** author 王广帅* since 2022/2/8 22:37*/ public class XyBeanCopyUtil {private static MapperFactory mappe…

Unity 组件学习记录:Aspect Ratio Fitter

概述 Aspect Ratio Fitter是 Unity 中的一个组件&#xff0c;用于控制 UI 元素&#xff08;如Image、RawImage等&#xff09;的宽高比。它在处理不同屏幕分辨率和尺寸时非常有用&#xff0c;可以确保 UI 元素按照预期的比例进行显示。当添加到一个 UI 对象上时&#xff0c;Aspe…

uni-app开发AI康复锻炼小程序,帮助肢体受伤患者康复!

**提要&#xff1a;**近段时间我们收到多个康复机构用户&#xff0c;咨询AI运动识别插件是否可以应用于肢力运动受限患者的康复锻炼中来&#xff0c;插件是可以应用到AI康复锻炼中的&#xff0c;今天小编就为您介绍一下AI运动识别插件在康腹锻炼中的应用场景。 一、康复机构的应…

Elasticsearch:什么是信息检索?

信息检索定义 信息检索 (IR) 是一种有助于从大量非结构化或半结构化数据中有效、高效地检索相关信息的过程。信息&#xff08;IR&#xff09;检索系统有助于搜索、定位和呈现与用户的搜索查询或信息需求相匹配的信息。 作为信息访问的主要形式&#xff0c;信息检索是每天使用…

Pytest-Bdd vs Behave:选择最适合的 Python BDD 框架

Pytest-Bdd vs Behave&#xff1a;选择最适合的 Python BDD 框架 Pytest BDD vs Behave&#xff1a;选择最适合的 Python BDD 框架BDD 介绍Python BDD 框架列表Python BehavePytest BDDPytest BDD vs Behave&#xff1a;关键区别Pytest BDD vs Behave&#xff1a;最佳应用场景结…

【数据集】5种常见人类行为检测数据集3379张YOLO+VOC格式

数据集格式&#xff1a;VOC格式YOLO格式 压缩包内含&#xff1a;3个文件夹&#xff0c;分别存储图片、xml、txt文件 JPEGImages文件夹中jpg图片总计&#xff1a;3379 Annotations文件夹中xml文件总计&#xff1a;3379 labels文件夹中txt文件总计&#xff1a;3379 标签种类数&am…

唯品会Android面试题及参考答案

HTTP 和 HTTPS 的区别是什么?你的项目使用的是 HTTP 还是 HTTPS? HTTP 和 HTTPS 主要有以下区别。 首先是安全性。HTTP 是超文本传输协议,数据传输是明文的,这意味着在数据传输过程中,信息很容易被窃取或者篡改。比如,在一个不安全的网络环境下,黑客可以通过网络嗅探工具…

基于Python+Vue开发的商城管理系统,大四期末作业,实习作品

项目简介 该项目是基于PythonVue开发的商城管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Python的网上商城管…

在 Solana 上实现 SOL 转账及构建支付分配器

与以太坊不同&#xff0c;在以太坊中&#xff0c;钱包通过 msg.value 指定交易的一部分并“推送” ETH 到合约&#xff0c;而 Solana 程序则是从钱包“拉取” Solana。 因此&#xff0c;没有“可支付”函数或“msg.value”这样的概念。 下面我们创建了一个新的 anchor 项目&a…

WebRTC搭建与应用(一)-ICE服务搭建

WebRTC搭建与应用(一) 近期由于项目需要在研究前端WebGL渲染转为云渲染&#xff0c;借此机会对WebRTC、ICE信令协议等有了初步了解&#xff0c;在此记录一下&#xff0c;以防遗忘。 第一章 ICE服务搭建 文章目录 WebRTC搭建与应用(一)前言一、ICE是什么&#xff1f;二、什么…

Linux高性能服务器编程 | 读书笔记 | 12. 多线程编程

12. 多线程编程 注&#xff1a;博客中有书中没有的内容&#xff0c;均是来自 黑马06-线程概念_哔哩哔哩_bilibili 早期Linux不支持线程&#xff0c;直到1996年&#xff0c;Xavier Leroy等人开发出第一个基本符合POSIX标准的线程库LinuxThreads&#xff0c;但LinuxThreads效率…