Python | Iter/genartor | 一文了解迭代器、生成器的含义\区别\优缺点

前提

一种技术的出现,需要考虑:

  1. 为了实现什么样的需求;
  2. 遇到了什么样的问题;
  3. 采用了什么样的方案;
  4. 最终接近或达到了预期的效果。

概念

提前理解几个概念:

  1. 迭代

    我们经常听到产品迭代、技术迭代、功能迭代,这里的迭代的意思是在现有产品、技术、功能等基础上进行研发升级的过程,就可以理解为迭代。

    从编程的角度讲,迭代是指重复执行一组操作的过程,通常是通过循环来实现。在编程中,迭代是遍历数据集合的一种方式,允许逐个访问集合中的元素。

  2. 可迭代

    可迭代是指能够被迭代的对象,即可以通过迭代遍历其元素的对象。Python中的可迭代对象可以是列表、元组、字典、字符串等。可迭代对象支持使用for循环进行迭代。

  3. 可迭代对象

    可迭代对象是实现了迭代协议的对象,即具有__iter__()方法的对象。该方法返回一个迭代器对象。

  4. 迭代器

    迭代器是一个具有__iter__()__next__()方法的对象,实现了迭代协议。__iter__()方法返回迭代器对象本身,而__next__()方法返回下一个元素。当没有元素可以迭代时,__next__()应该引发StopIteration异常。

    例如,可以使用内置的iter()函数将可迭代对象转换为迭代器。当使用next()函数从迭代器中获取下一个元素时,如果没有元素可供迭代,将引发StopIteration异常。

  5. 生成器

    生成器是一种特殊类型的迭代器,使用函数来生成值,而不是一次性构建并存储在内存中,所以极大减少内存使用。

    生成器函数使用yield语句产生值,而不是return。每次调用生成器的__next__()方法时,生成器函数会从上次yield语句的位置恢复执行,并继续执行直到遇到下一个yield语句或函数结束。

    这样可以有效地处理大量数据,因为它们允许逐个生成值,而不需要一次性生成整个序列。

普通实现

假设为了实现获取斐波那契数列的前多少位数据,一般采用的方式肯定是通过函数计算,将函数的计算值一次一次的返回并存储在一个如列表的容器中,然后再将容器数据遍历循环处理,代码示例如下:

def func(p_max):
    def get_data(a, b):
        return a + b

    a, b, count = 0, 1, 0
    p_lst = []
    while count < p_max:
        value = get_data(a, b)
        a, b, count = b, value, count + 1
        p_lst.append(value)
    return p_lst
return_values = func(1000000000)
print(return_values)
for i in return_values:
	# do something
  	pass

当p_max不大时,p_lst存储的数据不多,消耗的内存不大;一旦p_max比较大时,比如100,000,000时,p_lst存储内容就太多了,使用的内存也就会变得极大,甚至撑爆。
我们可以看一下当p_max设置为100,000,000时,30s内存就上限了。
在这里插入图片描述
这只是一个方法的使用,在开发系统时,如果存在这样的操作,系统说不定在什么时候就因为内存问题被撑爆了,这是很可怕的。

进一步优化

为了避免这样的内存爆炸问题,最好的方式就是不存储所有结果,计算出一个就消费一个,方法每次只返回一个值,然后将其他变量覆盖重用循环往复,这样不就不会存在内存问题了。

def get_data(a, b):
    return a + b
p_max = 1000000000
a, b, count = 0, 1, 0
while count < p_max:
    value = get_data(a,b)
    # do something use value
    print(value)

在这里插入图片描述

经过等待,发现内存并没有多少变化,说明确实有效果。

但从上面看出,这里的循环使用while进行判断操作,和我们通常使用的遍历方法用法不同,那么是否可以将上述方法采用遍历的形式计算产生并消费呢?迭代器

迭代器

首先我们自行创建一个,实现__iter__()__next__()方法:

class MyFb:
    def __init__(self, a, b, compute_time):
        # 斐波那契左数
        self.a = a
        # 斐波那契右数
        self.b = b
        # 计算多少次几次斐波那契值
        self.compute_time = compute_time
        # 初始化计次器
        self.current_time = 0

    def __iter__(self):
        return self

    def __next__(self):
        # 当计次器值 小于 总计算次数时,计算斐波那契数列值并将a、b向后推移
        if self.current_time < self.compute_time:
            value = self.a + self.b
            self.a = self.b
            self.b = value
            self.current_time += 1
            #print("%s/%s - %s" % (self.current_time,self.compute_time,value))
            return value
        else:
            raise StopIteration

# 斐波那契起始值0、1,共计次10次
myfb = MyFb(0, 1, 1000000000)
for i in myfb:
  	# do something
    print(i)
    
# while myfb:
#     try:
#         next(myfb)
#     except StopIteration as e:
#         print('End!')
#         break
# 斐波那契起始值0、1,共计次10次

我们已经可以对通过MyFB类创建的myfb迭代器对象进行逐一遍历获取、访问、处理;

但是,为了能使对象被遍历,需要创建一个类并手动进行内置函数__iter__()__next__()的定制,不仅徒增了代码量,也降低了代码的可读性。

有没有什么简单的方法就能实现迭代器的功能,还能保持代码易操作、易读的方式? 生成器

生成器

生成器(Generator)实际上是迭代器的一种特殊类型。(生成器只是一种便捷实现迭代器的方式而已)

通过使用函数yield 语句创建迭代器(每次执行完后会将global(), local() 环境变量缓存起来,下次执行时从yield处开始并恢复global(), local() 环境变量),每次遍历时,执行到yield时就将 yield后的结果返回回来,生成器函数并不使用return。

生成器具有迭代器的所有特性,并且还有一些额外的优势。

为了明显的说明其原理,我们写一个比较简单理解但是并不美观的代码:

def fun(p_max):
    a = 0
    b = 1
    count = 0
    while count < p_max:
        value = a + b
        yield value
        a = b
        b = value
        count += 1


g_values = fun(100000000)
print(g_values)
while True:
    try:
        p_value = next(g_values)
        print(p_value)
    except StopIteration:
        print('END!')
        break

在这里插入图片描述

因为for兼容了迭代器的异常处理,所以我们可以直接使用:

def fun(p_max):
    a = 0
    b = 1
    count = 0
    while count < p_max:
        value = a + b
        yield value
        a = b
        b = value
        count += 1

g_values = fun(10000000000)
print(g_values)
for i in g_values:
    print(i)

继续简化代码可以写为:

def fun(p_max):
    a,b,count = 0,1,0
    while count < p_max:
        yield a+b
        a,b,count = b,a+b,count+1

for i in fun(10000000000):
    print(i)

所以,迭代器是一种访问数据的方式,生成器是一种更加便捷实现的形式,而它们最主要的优点就是延迟数据生成,减少内存消耗,不要将其想的太复杂,只是一种措施、一种手段而已。

优缺点

生成器原本即为迭代器的特殊模式,所以可以看一下对于迭代器而言的优缺点都有哪些。

优点

  1. 内存效率: 迭代器按需生成元素,一次只产生一个元素,因此在处理大规模数据集时,迭代器能够更好地节省内存。这对于在有限内存环境中处理大型数据集或流式数据非常有利。
  2. 惰性计算: 迭代器支持惰性计算,只在需要时生成元素。这使得它们适用于处理无限序列或大规模数据,因为不需要一次性生成整个序列。
  3. 适用于多次迭代: 一旦迭代器耗尽了所有元素,可以通过重新调用 __iter__() 方法将其重新初始化,使得可以再次从头开始迭代。这使得迭代器适用于需要多次迭代相同数据集的情况。
  4. 支持 for 循环: 迭代器实现了迭代协议,因此可以直接用于 for 循环中。这样的代码更加简洁和易读。
  5. 支持无限序列: 由于迭代器是按需生成元素的,它们可以用于表示无限序列。这在模拟无限数据流等场景中非常有用。
  6. 支持并发操作: 由于迭代器是逐个产生元素的,它们可以在多线程或多进程环境中更容易地被共享和处理,而不需要担心整个数据集的同步问题。

缺点

  1. 不支持随机访问: 迭代器一次只能产生一个元素,无法通过索引直接访问元素。如果需要通过索引或其他方式随机访问数据集的元素,迭代器可能不是最佳选择。
  2. 代码繁琐: 相对于直接使用列表等数据结构,实现迭代器可能需要编写更多的代码,包括实现 __iter__()__next__() 方法。这可能在某些情况下增加了代码的复杂性。
  3. 不适用于所有场景: 尽管迭代器在许多情况下非常有用,但并不是所有问题都适合使用迭代器。在某些场景下,传统的数据结构和方法可能更加直观和简单。
  4. 一次性消耗: 大多数迭代器是一次性的,即一旦迭代器耗尽了所有元素,就不能重新开始。这与一些需要多次迭代相同的场景不匹配。
  5. 不易调试: 由于迭代器是按需生成元素的,当程序发生错误时,调试可能变得更加困难。在某些情况下,你可能无法直接查看整个数据集,而需要通过逐步迭代来定位问题。
  6. 不支持修改: 大多数迭代器是只读的,不支持在迭代过程中修改数据集。如果需要修改数据集,可能需要使用其他数据结构。





🎉如果对你有所帮助,可以点赞、关注、收藏起来,不然下次就找不到了🎉


【点赞】⭐️⭐️⭐️⭐️⭐️
【关注】⭐️⭐️⭐️⭐️⭐️
【收藏】⭐️⭐️⭐️⭐️⭐️

Thanks for watching.
Kenny

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

echarts 切换时出现上一次图形残留。

先说结果&#xff1a;悬浮高亮导致。这可能使echarts的bug。 正常情况出现这种问题&#xff0c;一般是setOption 中没有配置notMerge 导致新的配置与旧配置合并。 但是我这里始终配置notMerge: true&#xff0c;但仍然出现这种问题。 最后发现与鼠标悬浮有关。 环境 echar…

英飞凌TC3xx之一起认识GTM(九)GTM相关知识简述(CMU,CCM,TBU,MON)

英飞凌TC3xx之一起认识GTM(九)GTM相关知识简述(CMU,CCM,TBU,MON) 1 时钟管理单元(CMU)2 集群配置模块(CCM)3 时基单元(TBU)4 监控单元(MON)5 总结由前文的各篇内容,开发者已经知道如何使用GTM的大部分功能,在这些功能中,都需要一个信息就是fGTM 的数据,我们在前…

全网唯一值得推荐的C/C++框架和库

全网唯一值得推荐的C/C框架和库 C程序员开发指南 ​ 关注我&#xff0c;天天分享C/C开发技术干货&#xff01; ​关注他 30 人赞同了该文章 ​ 目录 收起 标准库 C通用框架和库 人工智能 异步事件循环 音频 生态学 压缩 并发性 容器 数据库 调试 游戏引擎 图…

(03)光刻——半导体电路的绘制

01、绘制精细电路的第一步 金属-氧化物半导体场效应晶体管(MOSFET)的革命,让我们可以在相同面积的晶圆上同时制造出更多晶体管。MOSFET体积越小,单个 MOSFET的耗电量就越少,还可以制造出更多的晶体管,让其发挥作用,可谓是一举多得。可见,制造更小的MOSFET成了关键因素…

原来圣诞树可以这么做

先看结果 从上到下依次是&#xff1a; 2^0 2^1 2^2 2^3 2^4 2^5 2^6 2^7 ... 依次排下去&#xff0c;最后加4个单位数的数字 原来代码的世界里还有这个美。^V^

十大电脑屏幕监控软件超全盘点!

电脑屏幕已经成为我们工作、学习和生活中不可或缺的一部分。然而&#xff0c;随着人们对电脑使用的日益频繁&#xff0c;电脑屏幕监控软件也应运而生&#xff0c;成为了企业和个人用户进行电脑管理和监控的重要工具。 本文将为您盘点十大电脑屏幕监控软件&#xff0c;帮助您了…

软件测试基础理论学习-常见软件开发模型

瀑布模型 背景 瀑布模型的概念最早在1970年由软件工程师Winston W. Royce在其论文《Managing the Development of Large Software Systems》中提出。Royce虽然没有明确提出“瀑布模型”这个术语&#xff0c;但他描述了一种线性的、阶段性的开发流程&#xff0c;各个阶段之间具…

技术概述:ARMv8体系结构

John Goodacre, Director Program Management ARM Processor Division, November 2011 背景&#xff1a;ARM体系结构 从ARM精简指令集体系结构提出到现在已经有20多年了&#xff1b;ARMv7系列处理器是在ARMv4基础上设计的&#xff0c;随着ARMv7系列处理器大量应用&#xff0…

图像分割-Grabcut法(C#)

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本文的VB版本请访问&#xff1a;图像分割-Grabcut法-CSDN博客 GrabCut是一种基于图像分割的技术&#xff0c;它可以用于将图像中的…

李沐机器学习系列5---循环神经网络

1 Introduction 对于样本的分析&#xff0c;通过全连接层处理表格数据&#xff0c;通过卷积神经网络处理图像数据&#xff1b;第一种假设&#xff0c;所有数据都是独立同分布的RNN 处理序列信号 序列数据的更多场景 1&#xff09;用户使用习惯具有时间的先后性 2&#xff09;外…

【Path的使用】Node.js中的使用Path模块操作文件路径

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;Node.js &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续前进的勇…

Leetcode算法系列| 12. 整数转罗马数字

目录 1.题目2.题解C# 解法一&#xff1a;模拟C# 解法二&#xff1a;硬编码数字 1.题目 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如&#xff0…

运算放大器(六):I-V 转换

1、跨阻放大器 放大器类型是根据其输入-输出信号的类型定义。假设放大器增益 &#xff08;X&#xff1a;输入&#xff0c;Y&#xff1a;输出&#xff09;。在电学范畴&#xff0c;由于用电压或电流表征一个信号&#xff0c;当输入信号为电流&#xff0c;输出信号为电压时&#…

MacOS14系统中Topaz Photo AI无法启动解决方法

MacOS14系统&#xff0c;在使用Topaz Photo AI是时无法启动&#xff0c;或者在 Mac电脑上导入图像后&#xff0c;Topaz Photo AI 应用程序窗口可能会冻结&#xff0c;怎么解决呢&#xff1f; 退出Topaz Photo AI for mac软件 回到电脑桌面&#xff0c;点击菜单栏前往-前往文件…

Prometheus 不能访问k8s的中的一些metrics的问题(controller-manager、scheduler、etcd)

主要有三个点 controller-manager、scheduler、etcd 参考&#xff1a; https://www.cnblogs.com/ltaodream/p/15448953.html kube-scheduler 在每台master节点执行 vim /etc/kubernetes/manifests/kube-scheduler.yaml 将 --bind-address127.0.0.1 改为 --bind-address…

Image - 体积最小的 base64 encode 1*1透明图片,透明背景图片base64编码

背景 前端开发时&#xff0c;有些<img>标签的src属性的值来源于接口&#xff0c;在接口获取结果之前&#xff0c;这个src应该设置为什么呢&#xff1f; 误区&#xff1a;设置为# 有人把src设置为<img src"#" />。 这是有问题的&#xff0c;浏览器解析…

理解UML中的依赖关系

理解UML中的依赖关系 在面向对象的设计中&#xff0c;理解各种类之间的关系对于构建一个清晰、可维护的系统至关重要。UML&#xff08;统一建模语言&#xff09;为我们提供了一种可视化这些关系的方式。今天&#xff0c;我们将深入探讨UML中的依赖关系&#xff08;Dependency&a…

脑电范式学习(一):Psychopy安装

脑电范式学习&#xff08;一&#xff09;&#xff1a;Psychopy安装 1 引言2 Psychopy软件3 安装教程4 花活儿5 总结 1 引言 可能有人会疑惑&#xff1a;为什么要去学Psychopy&#xff1f;Psychopy有什么好的&#xff1f; 首先&#xff0c;要告诉大家这么一个情况&#xff1a;现…

【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行环境搭建

【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 概述-CSDN博客 【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行环境搭建-CSDN博客 【大数据进阶第二阶段之Hadoop学习笔记】Hadoop 运行模式-CSDN博客 1、模板虚拟机环境准备 1.1、 hadoop100 虚拟机配置要求如下 &…

拟杆菌在肠道感染中的矛盾作用

谷禾健康 拟杆菌门细菌是革兰氏阴性菌的代表&#xff0c;具有外膜、肽聚糖层和细胞质膜。它们无氧呼吸的主要副产物是乙酸、异戊酸和琥珀酸。是最耐氧的厌氧菌之一。 参与人体结肠中许多重要的代谢活动包括碳水化合物的发酵、含氮物质的利用以及胆汁酸和其他类固醇的生物转化。…