迭代器和生成器
官方文档:
https://docs.python.org/zh-cn/3/tutorial/classes.html#iterators
https://docs.python.org/zh-cn/3/reference/expressions.html#generator-iterator-methods
可迭代对象
定义:实现了迭代协议的对象,能够使用for循环进行遍历的都叫可迭代对象。
Python中以下数据类型可以进行迭代:
- 字符串
- 列表
- 元组
- 集合
- 字典
可以通过以下方法检测是否可迭代:
from collections import Iterable
# 字符串
s = "test"
print(isinstance(s, Iterable)) # 输出True
# 列表
li = [1, 2, 3, 4]
print(isinstance(li, Iterable)) # 输出True
# 元组
tup = (1, 2, 3, 4)
print(isinstance(tup, Iterable)) # 输出True
# 集合
se = {1, 2, 3, 4}
print(isinstance(se, Iterable)) # 输出True
# 字典
dic = {"name": "test"}
print(isinstance(dic, Iterable)) # 输出True
迭代协议
定义:可迭代对象实现了__iter__方法,那么这个对象就实现了迭代协议。
# 我们可以发现这几种数据类型中都存在__iter__方法
if "__iter__" in dir("12334"):
print("True")
if "__iter__" in dir([1, 2]):
print("True")
if "__iter__" in dir((1, 2)):
print("True")
if "__iter__" in dir({1, 2}):
print("True")
if "__iter__" in dir({1:2}):
print("True")
# 打印一下这个方法,得到迭代器
print([1, 2].__iter__())
# 输出:<list_iterator object at 0x000001E4F58387F0>
# 查看列表迭代器相对于列表,多了哪些方法
print(set(dir([1, 2].__iter__())) - set(dir([1, 2])))
# 输出:{'__length_hint__', '__next__', '__setstate__'}
迭代器
定义
迭代器协议必须拥有__iter__和__next__方法。
实现了迭代器协议的对象叫迭代器。
注意点:所有的迭代器都是可迭代对象。
创建迭代器
- iter(可迭代对象)
li = ["id", "title", "url", "data", "expected"]
# 将列表转换成迭代器
itor = iter(li)
print(itor)
# 打印结果: <list_iterator object at 0x000002882C40C0B8>
# 通过next方法对迭代器进行迭代操作
print(next(itor)) # 输出:id
print(next(itor)) # 输出:title
print(next(itor)) # 输出:url
print(next(itor)) # 输出:data
print(next(itor)) # 输出:expected
print(next(itor)) # 报错:StopIteration
- 可迭代对象.
__iter__()
# 创建一个迭代器
iter_1 = [1, 2, 3, 4, 5].__iter__()
# 获取迭代器中元素长度
print(iter_1.__length_hint__()) # 输出:5
# 根据索引值指定从哪里开始迭代
print(iter_1.__setstate__(2)) # 输出:None
# 一个个获取迭代内所有值
print(iter_1.__next__()) # 输出:3
这里需要特别注意, range是可迭代的,但是不是一个迭代器。
from collections import Iterable, Iterator
print(isinstance(range(5), Iterable)) # 输出:True
print(isinstance(range(5), Iterator)) # 输出:False
迭代器的特性
- 迭代器能够使用next方法进行迭代操作。
- 当迭代器中所有数据被迭代完之后,再使用next去迭代会抛出异常StopIteration。
- 迭代器是不存储数据的,如果数据被遍历了,下次就无法遍历了。
迭代器的作用
- 节约内存,提升程序的性能
生成器
生成器是一种特殊的迭代器,具备迭代器所有的特性。生成器内部不存储数据,只保存生成数据的计算规则。
生成器的应用:
- 生成器表达式
- 生成器函数
注意:数据比较简单规律就用生成器表达式。 数据比较复杂就用生成器函数。
生成器表达式
生成器表达式其实就是推导式的另一种应用。可以理解为元组推导式,但是返回的不是一个元组,而是一个生成器对象。
# 生成器表达式
a = (f"第{i}个数" for i in range(3))
# 以下两种方式使用next方法都是一样的
print(a.__next__()) # 输出:第0个数
print(next(a)) # 输出:第1个数
生成器函数
定义:只要函数中定义了yield这个关键字, 那么这就是一个生成器函数。生成器函数在调用的时候是不会直接执行的,会返回一个生成器对象。
生成器函数的执行:
- 当我们使用next去获取生成器中的数据(使用next对生成器进行迭代操作),此时生成器函数会执行到yield处暂停,并且返回yield后面的数据。
- 当生成器中数据都获取完之后,继续使用next方法会报错StopIteration。
# 定义一个生成器函数
def produce():
for i in range(100):
yield "第{}个数".format(i)
# 创建一个生成器对象
g = produce()
print(g) # 输出:<generator object produce at 0x000001BDA33EDF68>
# 获取生成器中的数据
print(g.__next__()) # 输出:第0个数
print(g.__next__()) # 输出:第1个数
print(g.__next__()) # 输出:第2个数
生成器的作用
- 延迟计算,有助于大数据处理
- 提高代码可读性
- 节约程序内存,提高性能
生成器和迭代器的区别
- 生成器比迭代器多了三个方法:
- send方法
- close方法
- throw方法
- 生成器函数每次调用都会返回一个新的生成器,内存地址是不一样的。
send方法
- send方法在生成数据的同时,可以给生成器内部传参。
- send方法具有next方法功能的同时,还能进行传参操作。
- send相当于进阶版的next。
- send方法必须使用next生成过一次数据后才能调用,因为send方法只能从yield处开始执行。或者我们也可以使用send(None)启动生成器,再使用send方法发送实际值
- 如果直接使用send方法,会报错:TypeError: can’t send non-None value to just-started generetor。
def func():
for i in range(100):
s = yield i
print("send传进来的参数:", s)
# 创建生成器对象
f = func()
# 如果我们直接send会报错:TypeError: can't send non-None value to a just-started generator
# f.send(10)
# 使用send方法之前,必须先使用next方法生成一次数据
print(next(f)) # 输出0
print(f.send(1000)) # 输出send传进来的参数: 1000, 1
# 如果不传值也不会报错,只是会返回None
print(next(f)) # 输出send传进来的参数: None, 2
# ---------------或者这样启动生成器再send------------------------
f.send(None)
f.send(109) # 输出: send传进来的参数: 109
send方法的场景应用:
def fun():
num = 1
for i in range(100):
print(f"当前num的值{num}, i的值:{i}")
s = yield num * i
print(f"s的值:{s}")
# 如果s有值,返回s, 否则返回num
num = s or num
f = fun()
res = f.__next__()
print(res)
res = f.send(54)
print(res)
res = f.__next__()
print(res)
res = f.__next__()
print(res)
res = f.send(12)
print(res)
res = f.send("14")
print(res)
输出结果:
close方法
- 生成器可以调用close方法进行关闭
- 生成器关闭后就无法获取数据了。
- 生成器关闭后不能重启,要使用必须新开一个。
def func():
for i in range(100):
s = yield i
print("send传进来的参数:", s)
# 创建生成器对象
f = func()
print(next(f)) # 输出:0
# 关闭生成器
f.close()
print(next(f)) # 报错:StopIteration
throw方法
throw方法可以在生成器内部上一次暂停的yield处引发一个指定的异常类型,在生成器内部主动抛出异常。
def func():
for i in range(100):
try:
yield i
except TypeError:
yield "TypeError异常返回的数据"
except TimeoutError:
yield "TimeoutError异常返回的数据"
# 创建生成器对象
f = func()
print(next(f)) # 输出:0
# 在生成器内部主动抛出异常
# print(f.throw(TypeError)) # 输出:TypeError异常返回的数据
print(f.throw(TimeoutError)) # 输出:TimeoutError异常返回的数据
# 注意:这里不能同时throw两个异常,会报错。