迭代器,可迭代对象,生成器

news2024/11/27 17:43:48

目录

结论:

1:可迭代对象:

2:生成器:概念如下:

3:迭代器的定义:要同时满足以下三点

一:可迭代对象的分类

二:迭代器的意义和应用场景

1:迭代器的意义

2:迭代器的应用场景

a:用迭代器来构建数据管道

b: 数据生成器:但并不等同于yield所返回的生成器

三:生成器 

1:什么是生成器

a: 只能用在函数内;

b:在函数内任何一个地方出现了,yield,那么哪怕永远无法被执行到,函数都会发生变异;

2:yield是语句还是表达式

3:生成器的4个状态

a:当调用生成器函数得到生成器对象的时候,此时的生成器对象可以理解为初始状

b:通过next()调用生成器对象,对应的生成器函数代码开始运行;

c:如果遇到yield语句,next()返回时:

d:如果执行到函数结束,则抛出stopIteration异常:

4:用yield重构迭代器

四:生成器背后的运行机制

 1:生成器函数与普通函数之间的区别

a:普通函数

b:生成器函数

五:由此引出同步和异步的关系 

六:协程


本文主要参考B站系列文章:明明是生成器,却偏说是协程,你是不是在骗我? | Python AsyncIO从入门到放弃04_哔哩哔哩_bilibili

结论:

1:可迭代对象:

     若一个类含有__iter__方法并且__iter__方法返回的是一个迭代器对象,则称这个类为可迭代对象,也就是说,这个类本身不含有__next__方法,但是__iter__返回的对象是含有的;同时,其实我们说可迭代对象的__iter__方法返回一个生成器对象也是可以的,因为生成器是一种特殊的迭代器,见下文;

补充:for循环其实就是先通过__iter__方法返回一个迭代器对象,然后不断调用这个迭代器的next获取值,同时,for循环再找不到__iter__的时候,也可以找__getitem__,详细的参考本系列另一篇文章,定义__getitem__方法的类,也是可迭代的;

举例:比如python中的range()方法就是一个可迭代对象,她含有__iter__方法,并返回一个迭代器,但本身不含有__next__方法,所以a = range(1000),这里的a其实就是含有__iter__的可迭代对象,此时用b = a.__iter__(),则b就是一个迭代器,分别用dir()函数去查看a和b的属性可以直观看到两者的差别;

2:生成器:概念如下:

      a:如果一个函数含有yield关键字,那么这个函数叫做生成器函数;

      b:生成器函数返回的是生成器对象(generator),也就是说yield返回的是生成器对象,所以yield真实的操作是调用了python内置的generator类创建了一个对象并返回,而这个generator类本身也声明了__iter__和__next__方法,所以,我们可以说生成器其实属于迭代器的一种;

3:迭代器的定义:要同时满足以下三点

       a: 含有__iter__和__next__内置函数的类;迭代器必须同时实现这两个方法,这称之为迭代器协议;那根据这个协议,我们可以知道,迭代器一定是可迭代对象;

       b: __iter__内置函数要返回自身self;

       c: __next__方法返回下一个数据,如果没有数据了,则需要抛出一个stopIteration的异常

补充:迭代器的意义:

参考:15分钟彻底搞懂迭代器、可迭代对象、生成器【python迭代器】_哔哩哔哩_bilibili


一:可迭代对象的分类

参考自:Python 迭代器深入讲解 |【AsyncIO从入门到放弃#1】_哔哩哔哩_bilibili

    大致分为两类,一类是容器类型的(只含有__iter__),一类的迭代器类型的(同时含有__iter__和__next__)

这里的只能迭代一次,是指一次迭代完以后无法从头再来;比如列表,袁组这类的容器类型的可迭代对象,是可以多次迭代的,每次从头迭代都可以,但是迭代器类型只能迭代一次,比如range只能生成一个;

二:迭代器的意义和应用场景

1:迭代器的意义

2:迭代器的应用场景

a:用迭代器来构建数据管道

b: 数据生成器:但并不等同于yield所返回的生成器

三:生成器 

1:什么是生成器

我们把含有yield关键字的函数称为生成器函数,把调用生成器函数返回的结果称为生成器;生成器对象是迭代器,那么就必须满足上面迭代器的要求;

先说yield关键字:其有两个特点

a: 只能用在函数内;

b:在函数内任何一个地方出现了,yield,那么哪怕永远无法被执行到,函数都会发生变异;

这里所谓的变异是说,当你执行这个函数的时候,这个函数将不再直接运行,如下图,执行g = gen()的时候,并没有打印hello,也就是说这个函数并没有运行,二是返回了一个对象g,因为正常来说函数运行直接gen()就可以了,是没有返回值的。而这里返回了一个值,这个值是是一个generator对象;

再细看的话,生成器函数依然还是属于函数,不是生成器;

2:yield是语句还是表达式

yield关键字到底是语句还是表达式呢?最开始是语句,后来升级为表达式了;为什么要升级呢?因为为了实现协程,因此需要再原来的生成器基础上实现一个增强型的生成器;

这里我们先讲第一个,也就是作为语句时候的yield,可以看作是return;

yield关键字最根本的作用是改变了函数的性质:包括以下两点

a:调用生成器函数不是直接执行其中的代码,而是返回一个生成器对象;

b:生成器函数内的代码,需要用过生成器对象来执行;

因此,从这一点来说,生成器函数的作用和类是差不多的。

我们又说生成器对象实际上就是迭代器,所以其运行方式和迭代器是一致的:

a:通过next()函数来调用;

b:每次next()都会再遇到yield后返回结果,作为next()的返回值;

c:如果函数运行结束(即遇到了return),则抛出stopIteration的异常;

举例如下:

当不经过yield的时候:

这里遇到return,直接出发stopIteration异常,实际上,return在生成器里面的作用就是出发stopIteration这个异常,而且return的结果讲作为异常返回值被打印出来,如上图所示

当经过yield的时候:

 可以看到,yield的值是直接返回了的,而且此时函数停在yield语句出,当使用next语句的时候,代码将从这里继续运行;

3:生成器的4个状态

a:当调用生成器函数得到生成器对象的时候,此时的生成器对象可以理解为初始状

       生成器函数本身的内容也不会执行;

b:通过next()调用生成器对象,对应的生成器函数代码开始运行;

       此时生成器对象处于运行中状态;

c:如果遇到yield语句,next()返回时:

       a:yield语句右边的对象作为next()的返回值;

       b:生成器在yield语句所在的位置暂停,当再次使用next()时继续从该位置运行;

d:如果执行到函数结束,则抛出stopIteration异常:

       a:不管是使用了return语句显示的返回值,或者是默认的返回None值,返回值都只能作为异常的值一并抛出;

       b:此时的生成器对象处于结束的状态;

       c:对于已经结束的生成器对象再次调用next(),直接抛出stopIteration异常,并且不含返回值;

4:用yield重构迭代器

与和class定义的迭代器对比如下:

 

四:生成器背后的运行机制

 主要包含下面三个内容:

  • 生成器函数和普通函数之间的区别
  • 生成器对象和生成器函数之间的关系
  • 生成器函数可以“暂停”执行的秘密

 1:生成器函数与普通函数之间的区别

a:普通函数

     先说普通函数的运行机制,每当定义一个函数之后,我们就得到了一个“函数对象”,但实际上函数中的代码时保存在"代码对象"中的;

 

这里,再结合自己的实验展示如下:

 

 代码对象随着函数对象一起创建,是函数对象的一个重要属性,代码对象中重要的属性都是以co_开头,如上图所示;

但实际上,这个时候函数对象和代码对象只是保存了函数的基本信息,当函数运行的时候,还需要一个对象来保存运行时的状态,这个对象就是“帧对象”;每一次调用函数,都会自动创建一个帧对象,记录当前运行的状态;

可以利用inspect函数来得到函数的运行帧,通常情况下,这个帧对象它在函数运行结束的时候会被自动销毁,也就是被垃圾回收,但为了演示,这里将其保存为变量f1和f2如下:

 

这里用一个辅助函数show_backrefs来展示函数对象,代码对象和帧对象之间的关系如下:

 

code这里是代码对象,function这里是函数对象,frame这里是帧对象 ;从图上可以看到,函数对象和帧对象都对代码对象有引用;

帧对象中的重要属性都是以f_开头:

这里就引出了函数运行栈的概念,因为当一个函数调用另一个函数的时候,前一个函数还没有结束,所以这两个函数的帧对象是同事存在的。 所以,一个程序的运行期同时存在着很多个帧对象。

另外需要补充一点:一个线程只有一个函数运行栈

 

ok,那什么是函数运行栈呢?

如下图所示:

 

首先f1是bar返回的foo()函数,所以此时最上面的帧对象引用的代码时foo函数的代码,如最顶上frame的f_code所示,此时这个最顶层的frame其实是有上一个frame引用的,这个上一个frame就是bar函数的帧对象,我们可以看到第二个frame的f_code其实是bar函数;以此类推,bar呢这里又是有jupyter的帧调用的等等,这样下来,左边这一长串frame就是函数运行帧了;

b:生成器函数

 首先,生成器函数仍然是函数对象,当然也包括代码对象;但是,调用生成器函数不会直接运行(也就是说,调用生成器函数的时候不会像普通函数那样创建一个帧对象,并将这个帧对象压入函数栈)

关于帧对象到底在哪里,其实是保存在一个由python创建的全局帧栈中,也就是说从代码运行开始,python就创建了一个栈,然后不断将正在运行的代码的针对像进栈出栈,也就是函数不断运行

参考:深入理解Python函数调用和栈帧-皮蛋编程 (pidancode.com)

我们来看为什么调用生成器函数的时候不会直接运行:

 

如图:调用生成器函数,返回一个生成器generator,这个生成器有一个gi_frame属性,这个属性保留了一个帧对象frame的属性,这个帧对象和普通函数的帧对象差不多,保留了对生成器函数的代码对象的引用;

所以,为什么调用生成器的时候没有直接运行生成器函数的代码,因为生成器这里自带了一个帧,这个帧与普通函数的帧不同,普通函数的帧对代码的引用是通过f_code,而生成器这里则是用的gi_code,如上图所示;

也就是说,每次用next来对生成器进行迭代的时候,都是用这个帧对象gi_frame来保存状态,这个帧对象其实是没变的,这就是为什么生成器可以暂停,所谓的暂停就是因为它把这个帧给保存下来了,因为我们一般的函数运行完以后,整个函数的帧对象就出栈了;

我们看一下接下来这个例子:

可以看到gfg是一个生成器对象,此时由于func_a调用了next函数,所以相当于此时先把func_a的帧对象压入了python的帧栈,再把gfg的帧对象压入了帧栈, 因此此时的栈顶是生成器的帧对象,而生成器的帧对象gi_frame所指向的帧其实是生成器函数的帧(也就是前面说的,gi_frame实际上保存了生成器函数的frame),因此每次next生成器的时候,实际上都是去通过生成器的帧对象gi_frame去调用了生成器函数的帧对象,实验如下,上图是func_a调用生成器,再次用func_b调用生成器,结果是一样的,只不过此时生成器帧对象gi_frame指向的生成器函数的帧对象frame的上一级帧对象是func_b的帧对象;

另外可以看到不论是func_a的帧对象还是func_b的帧对象都有一个箭头指向了生成器,其实就是简单指明是一个引用关系;

那么,此时我么也可以理解yield的用处了,其实就相当于将生成器帧对象gi_frame指向的生成器函数的帧对象出栈,当迭代结束的时候,则将生成器的帧对象gi_frame也出栈;

总结如下:

五:由此引出同步和异步的关系 

普通函数的调用:

  • 调用函数:构建帧对象并入栈;
  • 函数执行结束:帧对象出栈并销毁 

因此如果想要用普通函数来同时运行多个任务,只能采用同步的方式,如下,任务b想要执行,必须等到任务a执行完毕才可以:

而生成器函数的调用则不同

  • 其先创建生成器函数帧对象frame,再创建生成器,构建生成器帧对象,并将生成器帧对象gi_frame指向生成器函数帧对象frame;但是,此时的生成器帧对象gi_frame并没有入栈
  • 其后,多次通过next出发执行,并将生成器帧对象入栈;
  • 然后,每次遇到yield,则执行生成器帧对象出栈,但是并不销毁;
  • 最后,当迭代结束的时候,生成器帧对象出栈并销毁;

这里,由于第2,3步时多次的出栈入栈,所以在2,3步之间,就可以由其他函数的帧对象进栈入栈; 于是,生成器函数就让异步运行任务变成了可能

所以,我们现在可以进一步去理解生成器函数所返回的生成器对象是什么了,其实生成器对象就是一个用来迭代执行生成器函数的迭代器; 

再进一步:

我们可以把迭代器分成两种:

一种是前面所说的数据的迭代器,针对一个包含很多元素的数据集,逐个返回其中的元素;

一种是这里所说的生成器迭代器,针对一个包含很多代码片段的函数,分段执行其中的代码;

那么基于上述的观点,我们说,当我们用生成器来实现迭代器的时候,如果我们的关注点是yield value所返回的value,那么,我们就将其理解为一个生成器就可以了,但如果,我们的关注点是集中在被迭代执行的代码上的时候,就应该对生成器有一个全新是视角,那就是协程

六:协程

参考:明明是生成器,却偏说是协程,你是不是在骗我? | Python AsyncIO从入门到放弃04_哔哩哔哩_bilibili

未完待续...... 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第一届“龙信杯”电子数据取证竞赛Writeup

目录 移动终端取证 请分析涉案手机的设备标识是_______。(标准格式:12345678) 请确认嫌疑人首次安装目标APP的安装时间是______。(标准格式:2023-09-13.11:32:23) 此检材共连接过______个WiFi。&#x…

STM32 DMA从存储器发送数据到串口

1.任务描述 (1)ds18b20测量环境温度存储到存储器(数组)中。 (2)开启DMA将数组中的内容,通过DMA发送到串口 存在问题,ds18b20读到的数据是正常的,但是串口只是发送其低…

面试打底稿⑥ 项目一的第二部分

简历原文 抽查部分 计算运费模块板块扩展性优化,采用责任链模式,实现不同地区间寄件的运费模板扩展的优化,为模块解耦,提高了系统的扩展性 短信模块设计,设计了短信发送数据模板的数据化存储,规范了发送短…

NSSCTF做题(5)

[NSSCTF 2022 Spring Recruit]babyphp 代码审计 if(isset($_POST[a])&&!preg_match(/[0-9]/,$_POST[a])&&intval($_POST[a])){ if(isset($_POST[b1])&&$_POST[b2]){ if($_POST[b1]!$_POST[b2]&&md5($_POST[b1])md5($_POST[b2])){…

A. Sequence with Digits

题目:样例: 输入 8 1 4 487 1 487 2 487 3 487 4 487 5 487 6 487 7输出 42 487 519 528 544 564 588 628 思路: 暴力模拟题,看这数据范围,有些人可能会被唬住,以为是高精度或者容易超时,实际上…

springboot和vue:七、mybatis/mybatisplus多表查询+分页查询

mybatisplus实际上只对单表查询做了增强(速度会更快),从传统的手写sql语句,自己做映射,变为封装好的QueryWrapper。 本篇文章的内容是有两张表,分别是用户表和订单表,在不直接在数据库做表连接的…

如何搭建团队知识库?试试新的工具和方法吧!

知识本身没有价值,只有被利用的知识才能发挥作用。我们经常见到有许多“宏伟”的团队知识库,但是从来没有人去用…… 搭建团队知识库 没有人用的团队知识库存在的问题是“我们知道所有问题的答案,就是不知道问题是什么”。如何建立团队知识库…

Rust冒泡排序

Rust冒泡排序 这段代码定义了一个名为 bubble_sort 的函数,接受一个可变的整数类型数组作为输入,然后使用嵌套的循环来实现冒泡排序。外部循环从数组的第一个元素开始迭代到倒数第二个元素,内部循环从数组的第一个元素开始迭代到倒数第二个元…

mysql面试题7:MySQL事务原理是什么?MySQL事务的隔离级别有哪些?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:MySQL事务原理是什么? MySQL事务的原理是基于ACID(原子性、一致性、隔离性、持久性)特性来实现的,具体原理如下: Atomicity(原子性):事务…

深入理解操作系统- - 进程篇(1)

目录 进程解释: process in memory(进程在内存中包含什么) : 并发的进程: 进程定义: 个人定义: 书本定义: 进程状态: 进程何时离开CPU: 内部事件: 外部事件: 进…

Backblaze发布2023中期SSD故障数据质量报告

作为一家在2021年在美国纳斯达克上市的云端备份公司,Backblaze一直保持着对外定期发布HDD和SSD的故障率稳定性质量报告,给大家提供了一份真实应用场景下的稳定性分析参考数据。 本文我们主要看下Backblaze最新发布的2023中期SSD相关故障稳定性数据报告。…

华为ensp单臂路由及OSPF实验

单臂路由及OSPF实验 1.1实验背景 在这个实验中,我们模拟了一个复杂的网络环境,该网络环境包括多个子网和交换机。这个实验旨在帮助网络工程师和管理员了解如何配置单臂路由和使用开放最短路径优先(OSPF)协议来实现不同子网之间的…

【网络安全】网络安全之信息收集和信息收集工具讲解,告诉你黑客是如何信息收集的

一,域名信息收集 1-1 域名信息查询 可以用一些在线网站进行收集,比如站长之家 域名Whois查询 - 站长之家站长之家-站长工具提供whois查询工具,汉化版的域名whois查询工具。https://whois.chinaz.com/ 可以查看一下有没有有用的信息&#xf…

数据集笔记:OpenCelliD(手机基站开放数据库)

下载数据的方式可见:【数据获取】全球最大手机基站开源数据库 1 读取数据 import pandas as pdpd.read_csv(C:/Users/16000/Downloads/454.csv/454.csv,headerNone,names[Radio,MCC,MNC,LAC/TAC/NID,CID,Longitude,Latitude,Range,Samples,Changeable1,Changeable…

前端开发和后端开发的一些建议

前端开发和后端开发是Web开发的两个方向 前端开发主要负责实现用户在浏览器上看到的界面和交互体验,包括HTML、CSS和JavaScript等技术。后端开发主要负责处理服务器端的逻辑和数据,包括数据库操作、服务器配置和接口开发等技术。 前端开发 前端开发需…

卫星图像应用 - 洪水检测 使用DALI进行数据预处理

这篇文章是上一篇的延申。 运行环境:Google Colab 1. 当今的深度学习应用包含由许多串行运算组成的、复杂的多阶段数据处理流水线,仅依靠 CPU 处理这些流水线已成为限制性能和可扩展性的瓶颈。 2. DALI 是一个用于加载和预处理数据的库,可…

LabVIEW开发虚拟与现实融合的数字电子技术渐进式实验系统

LabVIEW开发虚拟与现实融合的数字电子技术渐进式实验系统 数字电子技术是所有电气专业的重要学科基础,具有很强的理论性和实践性。其实验是提高学生分析、设计和调试数字电路能力,培养学生解决实际问题的工程实践能力,激发学生创新意识&…

leetCode 188.买卖股票的最佳时机 IV 动态规划 + 状态压缩

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。 注意:你不能同时参与多…

国庆中秋特辑(五)MySQL如何性能调优?下篇

目录 5.数据库维护6. 数据库调优工具7.数据库架构优化8.代码层面优化9. 硬件层面优化10. 数据库安全 MySQL 性能优化是一项关键的任务,可以提高数据库的运行速度和效率。以下是一些优化方法,包括具体代码和详细优化方案。 接下来详细介绍,共有…

Hudi第二章:集成Spark

系列文章目录 Hudi第一章:编译安装 Hudi第二章:集成Spark 文章目录 系列文章目录前言一、安装Spark1、安装Spark2.安装hive 二、spark-shell1.启动命令2.插入数据3.查询数据1.转换DF2.查询 3.更新4.时间旅行5.增量查询6.指定时间点查询7.删除数据1.获取…