Python 迭代器、生成器

本文深入解析Python中的可迭代对象、迭代器与生成器的概念及应用,对比其优缺点,详细介绍生成器的构建方式及其与迭代器的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、可迭代对象

  • 字面意思:
    • 对象:Python 中一切皆为对象(巧了 Java 也是(手动滑稽))
    • 可迭代:可更新迭代,重复、循环的一个过程,每次更新迭代都会获得新的内容
  • 专业角度:内部含有 '__iter__‘ 方法的对象
  • 目前学过的可迭代对象:str、list、tuple、dict、set、range、文件句柄等
  • 判断一个对象是否是可迭代对象:看是否有 '__iter__' 方法,dir() 可以获取一个对象的所有方法;或者使用 isinstance(object, collections.iterable) 来判断对象是否是可迭代对象的一个实例

在这里插入图片描述

  • 优点:
    • 存储的数据直接能显示,比较直观:比如直接 print 一个可迭代对象,就会调用 __str__ 方法(相当于 Java 中的 toString),把可迭代对象的值打印出来
    • 拥有较多的方法,操作方便:增删查改等
  • 缺点:
    • 占用内存:一旦创建了一个可迭代对象,就会将该对象的内容全部加载到内存中
    • 不能直接通过 for 循环,不能直接取值(通过索引、key等)。诸如通过 for i in iterable 这种形式获取元素实际上也是调用了 __iter__ 方法先将可迭代对象转换成迭代器再进行获取

二、迭代器

  • 字面意思:器,工具,迭代器也就是可以一直更新迭代取值的工具
  • 专业角度:内部含有 __iter__ 方法且含有 __next__ 方法的对象就是迭代器;或者使用 isinstance(object, collections.iterator) 来判断对象是否是可迭代对象的一个实例
  • 把一个可迭代对象转换成迭代器:使用 iter() 方法或使用对象的 __iter__ 方法
    在这里插入图片描述
  • 迭代器取值:使用 next() 方法或对象的 __next__ 方法;当迭代器的值去玩了继续取,就会报StopIteration异常,所以一般使用迭代器需要做异常处理
    在这里插入图片描述
  • 优点
    • 节省内存:迭代器并不会一次性将对象的值全部加载到内存中,而是需要时才加载(类似 sed)
    • 惰性机制:next 一次只取一个值,绝对不多取
  • 缺点:
    • 速度慢:需要一直 next
    • 不能回头:只能一直往下取值,取过的值没保存就没了`
    • 不能直观的看到里面的数据

三、可迭代对象与迭代器对比

  • 可迭代对象:
    • 私有方法多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等)
    • 直观,可以直接看到里面的数据
    • 占用内存
    • 不能直接通过循环迭代取值
    • 应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择
  • 迭代器:
    • 节省内存,按需取值
    • 可以直接通过循环迭代取值
    • 数据不直观,操作方法单一
    • 应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择

四、生成器

生成器的本质就是迭代器,唯一的区别是生成器是我们自己用代码构建的数据结构,迭代器是 Python 提供的,或者通过可迭代对象转化得来的

定义生成器的方式:

通过生成器函数构建生成器
def func():
		...
		yield
	
g = func()

在这里插入图片描述
这就是最简单的生成器函数。实际上这个 yield 就替代了 return,不仅将函数变成了生成器函数,还会将后面的值在调用 __next__ 的时候返回出来
在这里插入图片描述
也可以在一个函数里定义多个 yield
在这里插入图片描述
之前说过,生成器本质上还是迭代器,一个 yield 对应一个 next,当 next 的数量超过了 yield,就会报 StopIteration

yield 与 return 的区别

  • return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值
  • yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素

应用举例:

买 5000 个包子,假设这个老板很厉害,一下子就把 5000 个包子做出来卖给我们,可是我们只有 5 个人,一下子吃不完,那包子就会冷掉、臭掉、被丢掉浪费了

# 普通函数做包子
def func():
    l1 = []
    for i in range(1, 5001):
        l1.append(f'{i}号包子')
    return l1


ret = func()
print(ret)

如果这个老板可以在我们需要多少个包子就做出来多少个包子的话,这样做出来的包子就不会被浪费了(比如我们每个人一口气能吃 40 个包子,那每次就做 200 个包子):

# 使用生成器做包子
def gen_func():
    for i in range(1, 5001):
        yield f'{i}号包子'


ret = gen_func()
for i in range(200):
    print(next(ret))

除了 使用 next() 触发 yield 之外,生成器还有一种方法 send(),这个方法可以在调用 yield 的同时传值给生成器内部
在这里插入图片描述
可以看到在使用 next() 的时候,只能获取到 yield 的值,但不能传递值
在这里插入图片描述
在使用 send() 的时候,可以将参数传入生成器中使用

需要注意的是第一次不能直接调用 send() 传参,因为每次调用生成器的时候,实际上只会返回 yield 后面的内容,然后生成器就停止了(睡眠了?),而 send() 传入的参数要通过 yield 传入生成器中(每次调用生成器在 yield 停止,然后在 yield 恢复继续允许),第一次调用并没有 yield 给我们传入参数,可以使用 send(None),可以打断点自己分析一下
在这里插入图片描述
yield 会将它后面跟着的对象直接返回,如果它后面跟着的是可迭代对象,也可以使用 yield from 将这个可迭代对象变成迭代器返回

在这里插入图片描述
yield from 是将列表中的每一个元素返回,所以写两个 yield from 并不会有交替执行的效果

在这里插入图片描述

通过推导式构建生成器

列表推导式:
在这里插入图片描述
生成器表达式:和列表推导式差不多,把 [] 改成 () 即可
在这里插入图片描述

列表推导式和生成器推导式的区别:

  • 列表推导式比较耗内存,所有数据一次性加载到内存;而生成器表达式遵循迭代器协议,逐个产生元素
  • 得到的值不一样:列表推导式得到的是一个列表;生成器表达式获取的是一个生成器
  • 列表推导式一目了然,生成器表达式只是一个内存地址
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值