迭代器就是迭代取值的工具,每一次取值都可以保存上一次取值的最终状态。
迭代器的核心能力是通过 next() 方法逐个取出元素,这是遍历可迭代对象的底层逻辑。
可迭代对象
可迭代对象就是具有 __iter__ 方法的对象。
#字符串name = "dream"# <str_iterator object at 0x000001D2B239B220>print(name.__iter__()) #列表# <list_iterator object at 0x000001D2B239B220>print([1, 2, 3].__iter__())#元组# <tuple_iterator object at 0x000001D2B239B220>print((1, 2, 3).__iter__())#字典# <dict_keyiterator object at 0x000001D2B232A2F0>print({"name": "dream", "age": 18}.__iter__())#集合# <set_iterator object at 0x000001D2B236A840>print({1, 2, 3}.__iter__())
字符串、列表、元组、集合和字典既是可迭代对象又是迭代器对象。
迭代器基于索引的迭代取值,所有迭代的状态都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存。
优点
● 为序列和非序列类型提供了一种统一的迭代取值方式。
● 惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值。就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
缺点
除非取尽,否则无法获取迭代器的长度,只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next。若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。
name_iter = "dream".__iter__() #将字符串 "dream" 转换为迭代器对象print(name_iter.__next__()) # dprint(name_iter.__next__()) # rprint(name_iter.__next__()) # eprint(name_iter.__next__()) # aprint(name_iter.__next__()) # mname_iter = "dream".__iter__() print(name_iter.__next__()) # d
二、生成器
Python中的生成器是一种特殊的迭代器,可以在需要时生成数据,而不必提前从内存中生成并存储整个数据集。
通过生成器,可以逐个生成序列中的元素,而无需一次性生成整个序列。生成器在处理大数据集时,具有节省内存、提高效率的特点。
# 元组生成式g = (x * 2 for x in range(3))print(g) # <generator object <genexpr> at 0x1291d1e70>print(g.__next__()) # 0print(g.__next__()) # 2print(g.__next__()) # 4print(g.__next__()) # 报错,输出:StopIteration
遍历循环取值的时候如果到了最后一个元素不会报错的,在for 循环内部帮助我们处理了 StopIteration 这个错误。
关键字 yield可以暂时保存当前的状态并且卡住。
def my_generator(): yield 1 yield 2 yield 3# <generator object my_generator at 0x104307840>print(my_generator()) iter = my_generator()print(iter.__next__()) #1 print(iter.__next__()) #2print(iter.__next__()) #3
yield练习:
def eater(): print(f"当前开始吃饭") while True: # 通过eat_iter.__next__() 启动生成器 走到 yield 卡住 food = yield # eat_iter.send("鱼香肉丝") print(f"开始吃{food}") # 开始吃鱼香肉丝# <generator object eater at 0x1042a7840>print(eater())# 当前开始吃饭# Noneprint(eater().__next__())# 向生成器的 yield 中传值# 先调用函数获取得到一个生成器对象,这个生成器对象还没有启动eat_iter = eater()# 手动启动生成器eat_iter.__next__()# 向 yield 传值eat_iter.send("鱼香肉丝") # 开始吃鱼香肉丝eat_iter.send("宫保鸡丁") # 开始吃宫保鸡丁
使用装饰器帮助我们完成创建对象和启动:
def init(func): # func 接收被装饰的函数(这里是eater生成器函数) def inner(*args, **kwargs): # 接收被装饰函数的参数(这里eater无参数) # 1. 调用被装饰的函数,得到生成器对象 g = func(*args, **kwargs) # func就是eater,调用eater()生成生成器对象g # 2. 手动预激生成器:让生成器执行到第一个yield处暂停 next(g) # 等价于g.__next__(),启动生成器对象 # 3. 返回预激后的生成器对象 return g return inner # 装饰器返回内部函数
eater是一个无限循环的生成器函数(因为while True),核心逻辑是接收外部发送的 “食物名称”,并打印 “吃该食物”。
@init # 用init装饰器装饰eater,调用eater()时实际执行init(eater)()def eater(): print(f"当前开始吃饭") # 生成器被预激时执行这行 while True: # 无限循环,持续接收食物 # 关键点:yield在这里是“接收数据”的作用,执行到这里会暂停 food = yield # 等待外部send()发送数据,数据会赋值给food print(f"开始吃{food}") # 收到数据后执行这行
第一步:执行 eat_iter = eater()
因为eater被@init装饰,所以调用eater()实际是执行init(eater),返回的inner函数。
①、执行g = func(*args, **kwargs):func是eater,调用eater()生成一个未激活的生成器对象g。
②、执行next(g):启动生成器,触发eater函数执行:先打印当前开始吃饭;执行到food = yield处暂停(生成器等待接收数据);
③、inner函数返回预激后的生成器对象g,赋值给eat_iter。
第二步:执行 eat_iter.send("鱼香肉丝")
send()方法的作用是:向生成器发送数据,并让生成器从暂停的yield处继续执行:
①、把"鱼香肉丝"赋值给food(即food = "鱼香肉丝");
②、执行print(f"开始吃{food}") :打印开始吃鱼香肉丝;
③、再次进入while True循环,执行到food = yield处,再次暂停,等待下一次发送数据。