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

news2024/10/7 2:20:56

可迭代对象

定义

在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 = employee

emp = 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 = employee

    def __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 5
    print('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 5
    print('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 5
    print('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 5
    print('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 = 10
    while n >= 0:
        # 记住,这里不是将 yield n 赋值给变量 newvalue
        # newvalue 是用来接收 send 方法传递过来的参数的
        # yield n 是用来将 n 返回给调用者(next 方法或 send 方法)的
        newvalue = yield n
        # 判断是否用 send 方法传递参数,如果调用 next 方法,则传递过来的是 None
        if newvalue is not None:
            n = newvalue
        else:
            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, 1
    while True:
        yield num1
        num1, num2 = num2, num1 + num2


f = 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.coloradmin.cn/o/1015285.html

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

相关文章

(手撕)数据结构--->堆

文章内容 目录 一&#xff1a;堆的相关概念与结构 二&#xff1a;堆的代码实现与重要接口代码讲解 让我们一起来学习:一种特殊的数据结构吧&#xff01;&#xff01;&#xff01;&#xff01; 一&#xff1a;堆的相关概念与结构 在前面我们已经简单的学习过了二叉树的链式存储结…

城市管网污水监测方案,科技助力污水排放管理!

根据《国务院办公厅关于加强入河入海排污口监督管理工作的实施意见》各地要明确“水污染&#xff0c;谁治理”和政府兜底的原则&#xff0c;明确排污主体责任。根据排污口类型集中整治&#xff0c;划分主体。加大私设暗管借道排污的监察力度溯源主体责任。加强科技研发&#xf…

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

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

计网第四章(网络层)(八)(最短路径优先协议OSPF)

在第七节&#xff08;计网第四章&#xff08;网络层&#xff09;&#xff08;七&#xff09;_永无魇足的博客-CSDN博客&#xff09;我们总结了路由信息协议RIP。在最后我们提到了RIP协议有坏消息传的慢的问题&#xff0c;这是距离向量算法的本质决定的&#xff0c;所以这种问题…

c语言练习59:深入理解char类型的取值范围

深入理解char类型的取值范围 例如&#xff1a; #include <stdio.h> int main() {char a[1000];int i;for(i0; i<1000; i){a[i] -1-i;}printf("%d",strlen(a));return 0; }结果为255 ab以%d的形式打印结果为&#xff1a;300 而c由于unsigned char的取值范…

【CMU15-445 Part-9】Multi-Threaded Index Concurrency Control

Part09-Multi-Threaded Index Concurrency Control 多线程下索引的并发控制 Concurrency Control 强制所有访问数据结构的线程都使用某种协议或者某种方式。并发控制协议的概念&#xff1a;并发控制协议是一种当并发操作作用在一个共享对象上时DBMS用来确保correct的method。…

【杂记】git管理工具的相关应用

这里记录一些用git管理工具进行开发的命令&#xff0c;便于自己查看&#xff0c;我认为下面两篇博客写的很详细&#xff0c;但是为了自己方便查看&#xff0c;所以自己写了一些命令供自己进一步理解。gitee相对git来说更方便一些&#xff08;毕竟国内的不用担心墙&#xff09;&…

JavaEE 网络原理——TCP的工作机制(初篇 包含 UDP 协议的再次阐述)

文章目录 一、再次简述 UDP 协议二、再次简述 TCP 协议三、描述部分 TCP 内部的工作机制1. 确认应答2. 超时重传 前提&#xff1a; 在前面的文章中&#xff0c;我向大家分别简单介绍了 TCP 协议和 UDP 包装一个数据形成数据报发送信息。 除此之外&#xff0c;还通过代码编写了 …

QT中摄像头的使用

QT中摄像头相关类 摄像头的使用 QT中摄像头的使用主要分为三个方面&#xff0c;显示画面、抓取图片和视频录制。这三个方面对应着摄像模块的三种模式。模式如下&#xff1a; ConstantValueDescriptionQCamera::CaptureViewfinder0相机仅配置为显示取景器。QCamera::CaptureSt…

React入门

一、react开始 1、react是什么 用于构建用户界面的JavaScript库 操作DOM呈现页面 &#xff08;发送请求获取数据和处理数据不由react处理&#xff09;fessbook开发 2、为什么要学 原生js操作DOM繁琐、效率低 使用原生js直接操作DOM&#xff0c;浏览器会进行大量重绘重排 原…

计算机视觉面试题整理

1、介绍目标检测网络yolo系列以及ssd系列的原理&#xff0c;yolo对小目标检测不好的原因&#xff0c;除了缩小anchor外还可以如何改善&#xff1f; Yolo目标检测&#xff1a;YOLO是一种实时目标检测算法&#xff0c;其核心思想是将目标检测问题归为一个回归问题&#xff0c;直…

Android毕业设计,基于Android 语音朗读书籍管理系统

视频演示&#xff1a; 基于Android 语音朗读书籍管理系统 基于 Android 的语音朗读书籍管理系统可以提供用户管理书籍、朗读书籍的功能。以下是一个简单的步骤和功能列表&#xff1a; 用户注册和登录功能&#xff1a; 用户可以注册新账号或使用现有账号登录系统。用户信息可以包…

【rust/egui】(十一)使用rfd选择文件并使用serde_json进行序列化

说在前面 rust新手&#xff0c;egui没啥找到啥教程&#xff0c;这里自己记录下学习过程环境&#xff1a;windows11 22H2rust版本&#xff1a;rustc 1.71.1egui版本&#xff1a;0.22.0eframe版本&#xff1a;0.22.0上一篇&#xff1a;这里 rfd-Rusty File Dialogs 一个跨平台的…

只需3步部署Django项目到Kubernetes上

1. 目标 本文讲述了如何通过3步&#xff0c;把Django项目部署在K8S上。 本文适用读者&#xff1a; 了解Django项目的开发。了解K8S的用途。 2. 具体步骤 把一个Django项目部署在Kubernete环境上&#xff0c;只需以下3步&#xff1a; 创建镜像部署在Kubernetes环境上配置MyS…

linux命令查看谁在使用服务器的GPU

命令&#xff1a;查看GPU使用情况 nvidia-smi 可以知悉GPU占用情况和主要使用GPU的进程&#xff0c;如下图所示&#xff1a; 实时查看gpu使用&#xff1a; nvidia-smi -l 1 表示每隔1s刷新一下&#xff0c;数字可更改。 查看进程的归属者 方法一&#xff1a;ps -f -p pid…

360极速浏览器X终极奥义之——更改划词工具条的搜索为百度搜索 2023更新版

原文为2019版本&#xff0c;具体已失效&#xff0c;2023更新。 1.需要将 https://www.so.com/s?q%s&src360csex_zoned字符串对应的十六进制码替换为 https://www.baidu.com/s?wd%s&src360csex_z对应的十六进制码。 2.需要删除后面的"oned"以保证转换出来…

冒泡排序~

1、对应长度len 数组&#xff0c;需要进行 len -1 趟冒泡&#xff0c;每趟冒泡&#xff0c;将最大&#xff08;小&#xff09;元素排列到最后无序位置 2、每趟冒泡从第一个元素开始&#xff0c;邻近两两比较&#xff0c;找出最大元素 每一趟冒泡&#xff0c;都进行元素交换&am…

基于频谱信息的图像去噪与恢复——使用约束最小二乘方滤波法

大家好&#xff0c;我是带我去滑雪&#xff01; 随着科学技术的不断发展&#xff0c;信息的交流和获取已不再受到时空的限制&#xff0c;已经成为人们日常生活中不可或缺的一部分。图像作为人类信息交流中的重要载体&#xff0c;起着不可替代的作用。频谱图像去噪复原方法是一种…

Hive【Hive(一)DDL】

前置准备 需要启动 Hadoop 集群&#xff0c;因为我们 Hive 是在 Hadoop 集群之上运行的。 从DataGrip 或者其他外部终端连接 Hive 需要先打开 Hive 的 metastore 进程和 hiveserver2 进程。 Hive DDL 数据定义语言 1、数据库&#xff08;database&#xff09; 创建数据库 c…

YOLOv8『小目标』检测指南

前言 目前博主课题组在进行物体部件的异常检测项目&#xff0c;项目中需要先使用 YOLOv8 进行目标检测&#xff0c;然后进行图像切割&#xff0c;最后采用 WinCLIP 模型 进行部件异常检测 但是在实际操作过程中出现问题&#xff0c; YOLOv8 模型目标检测在大目标精确度不错&a…