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

目录

结论:

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.mzph.cn/news/91601.shtml

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

相关文章

红米手机 导出 通讯录 到电脑保存

不要搞什么 云服务 不要安装什么 手机助手 不要安装 什么app 用 usb 线 连接 手机 和 电脑 手机上会跳出 提示 选择 仅传输文件 会出现下面的 一个 盘 进入 MIUI目录 然后进入 此电脑\Redmi Note 5\内部存储设备\MIUI\backup\AllBackup\20230927_043337 如何没有上面的文件&a…

记一次springboot的@RequestBody json值注入失败的问题(字段大小写的问题)

有时候做后端开发时,难免会与算法联调接口,很多算法的变量命名时全部大写,在实际springmvc开发中会遇到无法赋值的问题。 先粘贴问题代码 entity类 Data NoArgsConstructor EqualsAndHashCode(callSuper true) ToString(callSuper true) …

应用在手机触摸屏中的电容式触摸芯片

触控屏(Touch panel)又称为触控面板,是个可接收触头等输入讯号的感应式液晶显示装置,当接触了屏幕上的图形按钮时,屏幕上的触觉反馈系统可根据预先编程的程式驱动各种连结装置,可用以取代机械式的按钮面板&…

Git分支管理

前言 文本将会向您介绍创建、查看、切换、合并、删除、合并、临时保存等分支管理操作 创建/查看/切换分支 [Fan_558VM-12-13-centos gitcode]$ git branch dev //创建分支 [Fan_558VM-12-13-centos gitcode]$ git branch //查看分支dev * master [Fan_558VM-12-13-centos…

ElasticSearch - 基于 JavaRestClient 操作索引库和文档

目录 一、RestClient操作索引库 1.1、RestClient是什么? 1.2、JavaRestClient 实现创建、删除索引库 1.2.1、前言 1.2.1、初始化 JavaRestClient 1.2.2、创建索引库 1.2.3、判断索引库是否存在 1.2.4、删除索引库 1.3、JavaRestClient 实现文档的 CRUD 1.3…

UE学习记录06----根据Actor大小自适应相机位置

背景: staticMesh 会根据业务需要随时变化,然后通过staticMesh的大小自适应相机位置,捕捉画面用来预览该模型,使模型在画布中不会太大导致显示不全,也不会太小 参考: UE实现相机聚焦物体功能_右弦GISer的…

机器学习小白理解之一元线性回归

关于机器学习,百度上一搜一大摞,总之各有各的优劣,有的非常专业,有的看的似懂非懂。我作为一名机器学习的门外汉,为了看懂这些公式和名词真的花了不少时间,还因此去着重学了高数。 不过如果不去看公式&…

数据结构--栈的实现

数据结构–栈的实现 1.栈的概念和结构: 栈的概念:栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Las…

PostMan的学习

PostMan的学习 目录 环境变量和全局变量接口关联内置动态参数以及自定义动态参数实现业务闭环Postman断言批量运行collection数据驱动之CSV文件和JSON文件测试必须带请求头的接口Mock Serviers 服务器Cookie鉴权NewmanPostManNewManjenkins实现接口测试持续集成 参考资料&am…

Kerberos常见报错汇总

一.kdb5_util: Password mismatch while reading master key from keyboard 1>.错误复现 2>.错误原因分析 在初始化Kerberos数据库时需要输入密码,2次密码输入不一致就会导致该错误。 3>.解决方案 重新执行"kdb5_util -r YINZHENGJIE.COM create -s…

Mendix中的依赖管理:npm和Maven的应用

序言 在传统java开发项目中,我们可以利用maven来管理jar包依赖,但在mendix项目开发Custom Java Action时,由于目录结构有一些差异,我们需要自行配置。同样的,在mendix项目开发Custom JavaScript Action时,…

数据集笔记:旧金山共享单车OD数据

数据地址:System Data | Bay Wheels | Lyft

使用不同尺寸的传感器拍照时,怎么保证拍出同样视场范围的照片?

1、问题背景 使用竞品机做图像效果对比时,我们通常都会要求拍摄的照片要视场范围一致,这样才具有可比性。之前我会考虑用同样焦距、同样分辨率的设备去拍照对比就可以了,觉得相机的视场范围只由镜头焦距来决定。 但如果对于不同尺寸的传感器…

【Java 进阶篇】MySQL 数据控制语言(DCL):管理用户权限

MySQL 是一个强大的关系型数据库管理系统,提供了丰富的功能和选项来管理数据库和用户。数据库管理员(DBA)通常使用数据控制语言(Data Control Language,简称 DCL)来管理用户的权限和访问。 本文将详细介绍…

定义现代化实时数据仓库,SelectDB 全新产品形态全面发布

导读:9 月 25 日,2023 飞轮科技产品发布会在线上正式召开,本次产品发布会以 “新内核、新图景” 为主题,飞轮科技 CEO 马如悦全面解析了现代化数据仓库的演进趋势,宣布立足于多云之上的 SelectDB Cloud 云服务全面开放…

数据结构——堆(C语言)

本篇会解决一下几个问题: 1.堆是什么? 2.如何形成一个堆? 3.堆的应用场景 堆是什么? 堆总是一颗完全二叉树堆的某个节点总是不大于或不小于父亲节点 如图,在小堆中,父亲节点总是小于孩子节点的。 如图&a…

华为ensp单臂路由及OSPF实验

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

从 低信噪比陆上地震记录 解决办法收集 到 走时层析反演中的折射层析调研

目录 (前言1) 关于背景的回答:(前言2) 现有的降低噪声, 提高信噪比的一些特有方法的论文资料 (传统策略):1. 关于波形反演与走时层析反演2. 折射层析3. 用一个合成数据来解释折射层析反演的思路4. 其他层析反演方法:5. 关于层析反演的一些TIPS (可补充)参考文献: 降噪有关资料参…

SpringBoot使用Docker并上传至DockerHub

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版,欢迎购买。点击进入详情 文章目录 1.系列文章2.构建docker镜像的方式3.docker操作3.1 安装docker3.2 查看docker镜像3.3 本地运行docker3.4 修改tag3.5 推送docker镜像3.6 远端server拉取d…

FOC控制算法

目录 一、FOC介绍 二、FOC基本概念 1、为什么是三相? 2、FOC矢量控制总体算法简述 3、为什么FOC不一定需要电流采样?参考链接 4、FOC的分类 (1)有感FOC与无感FOC 三、FOC中电流采样 参考链接 1、高端采样 2、低端采样 …