Python 可迭代对象、迭代器、生成器

可迭代对象

定义

在Python的任意对象中,只要它定义了可以返回一个迭代器的 __iter__ 魔法方法,或者定义了可以支持下标索引的 __getitem__ 方法,那么它就是一个可迭代对象,通俗的说就是可以通过 for 循环遍历了。Python 原生的列表,字符串,元祖,字典等都是可以迭代的,可以通过 dir() 函数查看,如以下查看字符串,可见其定义了 __getitem__ 方法

判断一个对象是否是可迭代对象

方法一:isinstance + Iterable

方法二:hasattr + __getitem__ 

from collections import Iterable
# 方法一
print(isinstance([], Iterable))  # 返回 True,说明是可迭代对象
# 方法二
print(hasattr({}, '__getitem__'))  # 返回 True,说明是可迭代对象

自定义可迭代对象

class Employee:def __init__(self, employee):self.employee = employeeemp = Employee(['zs', 'ls', 'ww'])# 正常情况下会报错,emp 不是一个可迭代对象
# TypeError: 'Employee' object is not iterable
for i in emp:print(i)

 实现 __iter__ 和 __getitem__ 方法中的一个或者两个

class Employee:def __init__(self, employee):self.employee = employeedef __getitem__(self, item):# item 是解释器帮我们维护的索引值# 在 for 循环中,自动从 0 开始计算print(item)return self.employee[item]emp = Employee(['zs', 'ls', 'ww'])# 正常情况下会报错,emp 不是一个可迭代对象
# TypeError: 'Employee' object is not iterable
for i in emp:print(i)

迭代器

迭代器对象

迭代器就是同时实现了 __next__ 和 __iter__ 方法(缺一不可)的对象。其中 __iter__ 方法返回迭代器自身, __next__ 方法不断返回迭代器中的下一个值,直到容器中没有更多的元素时,抛出 StopIteration 异常,以终止迭代 由定义可以知道,迭代器一定是可迭代对象,因为迭代器实现了 __iter__ 方法,满足了可迭代对象的定义。但是可迭代对象不一定是迭代器,因为不一定实现了 __next__ 方法。

判断一个对象是迭代器

from collections import Iterator
print(isinstance(counter, Iterator))  # True
print(isinstance([], Iterator))  # False

为什么有了可迭代对象,还要有迭代器?

因为迭代器采用了工厂模式,节约了内存空间。所谓的工厂模式,就是在需要的时候,才会去生产数据,而不是像可迭代对象那样一次性 全部把数据生产出来。可迭代对象如列表,字典等,会事先把所有的数据生产并保存起来,而迭代器则是在每次获取下一个值的时候 才会返回值,所以迭代器没有长度这一说法,没有长度这一属性,如果获取完了会抛出 StopIteration 异常来表示没有数据了

所以迭代器适用于那种无限序列,不会占用很大的内存空间

from itertools import count# count 是一个迭代器
# 创建一个从 10 开始的无限序列
counter = count(start=10)
print(type(counter))  # <class 'itertools.count'>
print(dir(counter))  # 有 __iter__ 和 __next__ 方法print(next(counter))  # 10
print(next(counter))  # 11# 报错,没有长度属性
# TypeError: object of type 'itertools.count' has no len()
# print(len(counter))

可迭代对象转为迭代器

a = [1, 2, 3, 4]
print(type(a))  # <class 'list'>a_iter = iter(a)
print(type(a_iter))  # <class 'list_iterator'>

迭代器的特点

a = [1, 2, 3, 4]
a_iter = iter(a)
# 每一次获取迭代器中的值之后,都是把该值从迭代器中拿出来,即迭代器中已经没有该值了,因为已经被拿出来了
# 所以遍历完一次迭代器之后,不能遍历第二次
# 因为遍历第一次的时候已经把值拿出来完了,第二次去遍历的时候迭代器中啥也没有了
# 即迭代器不走回头路,可迭代对象则没有这种特点
for i in a_iter:print(i)for i in a_iter:print(i)# 上面代码只会输出一次 1 2 3 4# 前面说的,当遍历完迭代器的最后一个数据时,再获取数据抛出异常,
# 而 for 循环没有抛出的原因是
# for 循环内部也是用 next() 方法获取迭代器中的值,
# 当抛出异常后 for 循环会对异常进行处理,所以我们采用 for 循环遍历迭代器时不会有异常抛出

生成器

生成器其实是一种特殊的迭代器,但是这种迭代器更加优雅,因为不需要再像普通迭代器那样定义 __next__ 和 __iter__ 方法,只需要一个 yield 关键字,就会自动在内部帮我们实现这两个方法。所以,如果一个函数包含一个或多个 yield 关键字, 这个函数就会变为一个生成器。因为生成器是特殊的迭代器,所以它具备迭代器具备的特性

def demo():print('hello')yield 5print('world')print(type(demo()))  # <class 'generator'>
print(dir(demo))  # 有 __iter__ 和 __next__ 方法

yield 关键字的作用

1、程序每次在代码中遇到 yield 关键字后,会返回结果,相当于 return 5,但是并没有真的退出程序,而是保留当前函数的运行状态

2、返回结果后,保留当前函数的运行状态,等待下一次调用,下次调用时,从上一次返回结果处开始执行 

第二个作用非常重要,这意味着程序控制权的转移是临时和自愿的,函数将来还会收回控制权(在下次调用生成器的时候收回), 这也是 yield 和 return 最大的区别。return 意味着函数彻底交出控制权并结束运行,下一次调用将固定从函数的第一行代码开始执行。

def demo():print('hello')yield 5print('world')# 此时运行demo(),相当于调用函数,但是并不像普通函数那样马上执行,可以看控制台没有输出
# 此处只是生成一个生成器对象
c = demo()# 利用 next 方法调用生成器
# 第一次调用,打印 hello ,并返回 5
print(next(c))
# 第二次调用,打印 world ,并抛出异常 StopIteration(迭代器特性)
# 因为已经没有语句可以执行了,相当于数据已经取完了
print(next(c))

通过 send 向生成器传递数据

send 方法作用:

1、像 next 方法一样去调用生成器(调用生成器的两种方法:next 方法和 send 方法)

2、send 方法在调用生成器时,可以同时给生成器传递数据到生成器内部 

def demo():print('hello')# 注意,此处不是把 yield 5 赋值给变量 t# yield 5 是返回给调用者的值,即返回给 next(c),# 所以执行 print(next(c)) 语句时会在输出 hello 后输出 5# 而变量 t 是接收下一次调用(c.send('test'))时 send 方法传入的 test# 即 t = 'test't = yield 5print('world')print(t)c = demo()
# 第一次调用生成器,得到 t = yield 5 等号左边的表达式结果,即得到 5
print(next(c))
# 第二次调用,从 t = yield 5 语句开始执行,通过 send 方法把 test 传递给 t = yield 5 等号右边的变量 t
# 然后接着执行函数中两个打印语句,输出 world 和 test
# 打印之后就会抛出异常 StopIteration,因为已经没有要执行的语句了
c.send('test')

生成器的预激活机制

def demo():print('hello')t = yield 5print('world')print(t)c = demo()
# next(c) 就是生成器的预激活机制,即第一次调用
# 第一次调用也可以通过 send 方法,但是参数必须为 None
# 因为生成器没有办法在没有激活的情况下接收一个参数
# print(next(c))  # 预激活方式一
c.send(None)  # 预激活方式二
c.send('test')

查看生成器的运行过程

将下面代码的每一行打上断点(如图),进入debug模式可以清晰的看到代码的运行过程。

def countdown(n):print('counting down from ', n)  # 只在第一次调用生成器的时候输出,此时 n = 10while n >= 0:# 记住,这里不是将 yield n 赋值给变量 newvalue# newvalue 是用来接收 send 方法传递过来的参数的# yield n 是用来将 n 返回给调用者(next 方法或 send 方法)的newvalue = yield n# 判断是否用 send 方法传递参数,如果调用 next 方法,则传递过来的是 Noneif newvalue is not None:n = newvalueelse:n -= 1# 获得生成器对象 c,把 10 传递给 n
c = countdown(10)for i in c:# 第一次调用生成器# 第一次执行 for i in c 语句时,内部第一次调用 next(c)# 执行 print('counting down from ', n) 语句,此时 n=10# 然后进入 while n >= 0 循环,执行 newvalue = yield n 等号右边的 yield n,即返回 n=10 给i# 所以第一次for循环 i=10,输出 10# 第三次调用生成器# 第二次执行 for i in c 语句时,内部第二次调用 next(c),此时已经第三次调用生成器,第二次是用 send 方法调用的# 接着第二次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数# 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = 2# 进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=2# 所以第二次for循环 i=2,输出 2# 第四次调用生成器# 第三次执行 for i in c 语句时,内部第三次调用 next(c)# 接着第三次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数# 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = 1# 进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=1# 所以第三次for循环 i=1,输出 1# 第五次调用生成器# 第四次执行 for i in c 语句时,内部第四次调用 next(c)# 接着第四次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数# 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = 0# 进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=0# 所以第四次for循环 i=0,输出 0# 第六次调用生成器,最后一次# 第五次执行 for i in c 语句时,内部第五次调用 next(c)# 接着第五次调用生成器的地方开始执行,即从 newvalue = yield n 开始执行,next 方法没有给生成器传递参数# 所以 newvalue 为 None,则执行 else 语句:n -= 1,所以 n = -1# 不能进入 while n >= 0 ,那么生成器运行到最后结束,抛出一个异常 StopIteration# 抛出的异常被for循环内部处理,所以运行到此结束,for循环也结束print(i)  # 分别输出 10 2 1 0# 第一次进入 for 循环时 i = 10,所以进入 if 判断if i == 10:# 第二次调用生成器# 调用 send 方法调用生成器,接着第一次调用的地方继续执行# 把参数传递给 newvalue = yield n 中的 newvalue,此时 newvalue=3# newvalue 不为 None,进入判断 if newvalue is not None,将 n 设置为 newvalue (n = newvalue)# 此时 n = 3,进入 while n >= 0 ,然后返回newvalue = yield n 等号右边的 yield n,即返回 n=3# 然后进行第二次 for 循环print('send: ', c.send(3))  # 打印 send:  3

获得生成器的第二种方式

除了通过 yield 关键字得到生成器外,还可以通过小括号的形式得到,如下

"""
生成器表达式和列表推导式的区别:列表推导式会一下子将所有的数据生产出来,并放到列表中生成器表达式一次只生产一个数据获得生成器的两种方式:1、将普通函数里的 return 替换成 yield ,这样调用函数时会得到一个生成器2、利用生成器表达式
"""# 可以通过元祖表达式(小括号)得到生成器
a = (i for i in range(5))
# 列表推导式得到的是一个列表
b = [i for i in range(5)]# a 是生成器
print(type(a))  # <class 'generator'>
# b 是列表
print(type(b))  # <class 'list'># 利用推导式获得生成器的方式称为生成器表达式
t = (i * 2 for i in range(5))
print(t)  # <generator object <genexpr> at 0x000001F466254620>
print(next(t))  # 0
print(next(t))  # 2
# 把剩下的数据处理,目前只剩下 2 3 4
for i in t:print(i)  # 4 6 8

生成器实现斐波那契数列 

# 用生成器实现斐波那契数列
def fib():num1, num2 = 0, 1while True:yield num1num1, num2 = num2, num1 + num2f = fib()
# 因为 fib 函数里是死循环,所以只要调用 next(f) 就可以一直得到 斐波那契数列的值
print(next(f))  # 0
print(next(f))  # 1
print(next(f))  # 1
print(next(f))  # 2
print(next(f))  # 3
print(next(f))  # 5

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

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

相关文章

【小记录】jupyter notebook新版本

手欠升级 &#x1f605;今天手贱&#xff0c;在anaconda navigator里面更新了最新版本的spyder&#xff0c;然后莫名奇妙地jupyter notebook就打不开了&#x1f605;&#xff0c;报错说缺少模块”ModuleNotFoundError: No module named jupyter_server.contents“&#xff0c;…

从0到1学会Git(第三部分):Git的远程仓库链接与操作

写在前面:前面两篇文章我们已经学会了git如何在本地进行使用&#xff0c;这篇文章将讲解如何将本地的git仓库和云端的远程仓库链接起来并使用 为什么要使用远程仓库:因为我们需要拷贝我们的代码给别人以及进行协同开发&#xff0c;就需要有一个云端仓库进行代码的存储和同步&a…

【数据结构】C++实现AVL平衡树

文章目录 1.AVL树的概念2.AVL树的实现AVL树结点的定义AVL树的插入AVL树的旋转左单旋右单旋左右双旋右左双旋插入代码 AVL树的验证AVL树的查找AVL树的修改AVL树的删除AVL树的性能 AVL树的代码测试 1.AVL树的概念 二叉搜索树虽然可以提高我们查找数据的效率&#xff0c;但如果插…

模拟实现链式二叉树及其结构学习——【数据结构】

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 之前我们实现了用顺序表完成二叉树(也就是堆)&#xff0c;顺序二叉树的实际作用就是解决堆排序以及Topk问题。 今天我们要学习的内容是链式二叉树&#xff0c;并且实现链式二叉树&#xff0c;这篇博客与递归息息相关&a…

Keepalived+LVS高可用集群

目录 一、keepalived介绍&#xff1a; 二、keepalived工具介绍&#xff1a; &#xff08;1&#xff09;管理 LVS 负载均衡软件&#xff1a; &#xff08;2&#xff09;支持故障自动切换&#xff1a; &#xff08;3&#xff09;实现 LVS 负载调度器、节点服务器的高可用性&…

合宙Air724UG LuatOS-Air LVGL API控件-二维码(Qrcode)

二维码&#xff08;Qrcode&#xff09; 示例代码 qrcodelvgl.qrcode_create(lvgl.scr_act(),nil)lvgl.qrcode_set_txt(qrcode,"https://doc.openluat.com/home")lvgl.obj_set_size(qrcode,400,400)lvgl.obj_align(qrcode, nil, lvgl.ALIGN_CENTER, 0, 0)创建 可以通…

【Nginx25】Nginx学习:连接限制和请求限制

Nginx学习&#xff1a;连接限制和请求限制 之前我们就已经学习过了一些和流量限制相关的配置指令&#xff0c;它们是 HTTP 核心配置中的内容 &#xff0c;不记得的小伙伴可以回去看一下 Nginx学习&#xff1a;HTTP核心模块&#xff08;七&#xff09;请求体与请求限流https://m…

第3章_瑞萨MCU零基础入门系列教程之开发环境搭建与体验

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

【网络编程】TCP Socket编程

TCP Socket编程 1. ServerSocket2. Socket3. TCP的长短连接4. Socket 通信模型5. 代码示例&#xff1a;TCP 回显服务器 流套接字&#xff1a; 使用传输层TCP协议 TCP: 即Transmission Control Protocol&#xff08;传输控制协议&#xff09;&#xff0c;传输层协议。 TCP的特点…

一文详解二叉搜索树

数据结构-二叉查找树 前言 **摘自百度百科&#xff1a;**二叉查找树&#xff08;Binary Search Tree&#xff09;&#xff0c;&#xff08;又&#xff1a;二叉搜索树&#xff0c;二叉排序树&#xff09;它或者是一棵空树&#xff0c;或者是具有下列性质的二叉树&#xff1a; 若…

JDK8特性——接口增强函数式接口Optional方法引用

文章目录 接口增强默认方法静态方法 函数式接口SupplierConsumerFunctionPredicate Optional 类以前对null 的处理Optional的基本使用Optional的常用方法 方法引用方法引用的格式对象名::方法名类名::静态方法名类名::引用实例方法类名::构造器数组::构造器 接口增强 在JDK8之…

智能远程监考方案助力企业考试化繁为简

在音视频数字化之旅中&#xff0c;轻装上阵。 近年来&#xff0c;在数字化浪潮之下&#xff0c;远程考试频繁成为各领域热词&#xff0c;各企业也纷纷改革求新&#xff0c;将原本的企业内部考试转移到线上&#xff0c;从而获取更低廉的组考成本&#xff0c;更高的管理效率&…

深度学习基础之梯度下降

1. 引言 梯度下降是一种用于最小化&#xff08;或最大化&#xff09;损失函数的优化算法。它是机器学习和深度学习中的一个关键概念&#xff0c;通常用于调整学习算法中的参数。 梯度下降背后的核心思想是迭代调整参数以最小化损失函数。它的工作原理是计算损失函数相对于每个…

【LeetCode-中等题】18. 四数之和

文章目录 题目方法一&#xff1a;双指针&#xff08;定2动2&#xff09; 题目 方法一&#xff1a;双指针&#xff08;定2动2&#xff09; 这题可以参考【LeetCode-中等题】15. 三数之和 区别在于&#xff0c;三数之和只需要用一个for循环定住一个数&#xff0c;然后设置两个前…

数据结构与算法(C语言版)P4---顺序表、链表总结

顺序表和链表&#xff08;双向带头链表&#xff09;的区别 顺序表&#xff1a; 优点&#xff1a; 支持随机访问。需要随机访问结构支持算法可以很好的使用。cpu高速缓存利用率&#xff08;命中率&#xff09;更高。存储密度高 缺点&#xff1a; 头部中部插入删除时间效率低。…

合宙Air724UG LuatOS-Air LVGL API控件-窗口 (Window)

窗口 (Window) 分 享导出pdf 示例代码 win lvgl.win_create(lvgl.scr_act(), nil) lvgl.win_set_title(win, "Window title") -- close_btn lvgl.win_add_btn_right(win, "\xef\x80\x8d") -- --lvgl.obj_set_event_cb(cl…

典型数据结构-图,图的存储、基本操作和遍历

图 引自&#xff1a;《数据结构教程》。 概念 图可以使得元素之间的关系是 多对多。图中任意两个数据元素之间都可能存在连接关系。图作为一种数据结构&#xff0c;可以表达数据元素之间广泛存在着的更为复杂的关系。在众多应用之中&#xff0c;如电子线路分析、工程计划分析、…

Junit单元测试异常处理方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Junit单元测试异常处理方法 前言案例准备一、类方法内处理异常二、测试方法中处理异常1.try/catch/finally 语句2.Test(expected)3.ExpectedException 前言 提示&#xff1a…

深度解析shell脚本的命令的原理之mv

mv 是 Unix 或 Linux 中的一个基本命令&#xff0c;用于移动或重命名文件和目录。以下是对这个命令的深度解析&#xff1a; 基本操作&#xff1a;mv 命令的基本操作是将一个或多个源文件或目录移动到一个目标文件或目录&#xff0c;或者重命名源文件或目录。这是通过改变文件系…

银河麒麟--国产操作系统-九五小庞

那么&#xff0c;我国国产操作系统现状到底如何呢&#xff1f; 自 1999 年徐冠华部长一语点破我们的产业软肋之后&#xff0c;国产操作系统起步于国家“七五”计划期间&#xff0c;目前国产操作系统均是基于Linux内核进行的二次开发&#xff0c;中国国产操作系统进入Linux元年…