python 可迭代对象、迭代器、生成器详解
学习目标
-
学习迭代器是为了学习生成器,学习生成器是为了学习协程,协程就是实现多任务的一种方式
-
知道什么是可迭代对象,能够使用isinstance检测对象是否可迭代
-
知道使用iter函数可以获取可迭代对象的迭代器
-
知道使用next函数可以获得迭代器数据
一、可迭代对象、迭代器、生成器三者之间的联系
先看一张图:
从上图中可以看出:
Iterable(可迭代对象)、
Iterator(迭代器)
、genetator(生成器)关系如下:
- 可迭代对象 有 __iter__方法
- 迭代器继承了可迭代对象,有 __iter__、__next__ 这两个方法
- 生成器又继承了迭代器,有 send、close、 __iter__、__next__ 等方法
二、可迭代对象详解
1、介绍:
可迭代对象就是能够通过for循环迭代,逐一返回其成员项的对象称为可迭代对象, python中可迭代对象包括
-
所有序列类型 :如
list
、str
和tuple
、range
-
非序列类型:
dict
、set
、文件对象
-
实现了
__iter__()
方法的任意对象 -
实现了序列语义的
__getitem__()
方法的任意对象。 注意点:实现了`__getitem__`方法的被称为可迭代对象(支持迭代操作),但不是Iterable类型
2、__iter__()即(迭代协议)
__iter__()
在python中也被称作迭代协议,只要对象拥有 __iter__()
方法,那么该对象就实现了迭代协议,就可以进行迭代操作。
__iter__()方法的返回值必须是一个迭代器(后面讲解迭代器)
如下代码:自定义可迭代对象类
class MyList: def __iter__(self): return iter([1,2,3,4,5]) ml = MyList() for i in ml: print(i)
执行结果:
1
2
3
4
5
3、 __getitem__()方法
__getitem__()
是用来实现序列类型数据索引取值的魔术方法。python中的str、list、dict等类型的数据均实现了该方法。
li = [11,22,33] dic = {'a':11,'b':22} # 列表索引取值,本质上调用的是 li.__getitem__(1)方法去取值的。 li[1]
如下代码:自定义可迭代对象类
class Mylist2: """自定义的序列类类""" li = ['a1', 'a2', 'a3', 'a4'] def __getitem__(self, item): # iten是for循环内部传进来的索引值,从0开始 return self.li[item] if __name__ == '__main__': m2 = Mylist2() for i in m2: print(i)
执行结果:
a1
a2
a3
a4
从上面的案例中我们可以看到我们在遍历Mylist2这个类的对象时,其实就是不断的调用对象的__getitem__
方法来获取遍历出来的值
4、for循环迭代到底干了啥
4.1 __iter__()
当我们用for去遍历任何一个对象时,for循环执行的时候,会先去调用对象的 __iter__()
,根据__iter__()
返回的迭代器,再进行迭代操作
4.2 __getitem__()
for循环遍历对象的时候,会先去调用对象的 __iter__()
方法,如果对象没有定义 __iter__()
方法,那么for在遍历的时候,就会从索引 0 开始,循环调用__getitem__()
,,把__getitem__()
的返回值,作为遍历出来的数据,直到__getitem__()
中抛出异常,则终止循环。
简写for循环的功能:
a = [1,2,3,4,5] a = iter(a)#此时a已经变成迭代器了 while True: try: print(next(a)) except StopIteration: break
执行结果:
1
2
3
4
5
5、内置函数iter()和next()
上面代码例子中有提到iter内置函数,来了解以下
5.1 itet()--返回迭代器对象
内置函数 iter() 通过可迭代对象 返回一个关于可迭代对象的迭代器对象,其函数原型为:
iter(object[, sentinel])
其中,第一个参数 object 必须是 支持迭代协议 (有 __iter__() 方法) 或 支持序列协议 (有 __getitem__() 方法) 的集合对象。若不支持这些协议,会引发 TypeError。只传入第一个参数 object 并返回一个迭代器
注意,若还传入第二个参数 sentinel,则参数 object 必须是一个 可调用 (callable) 的对象 (如函数)。这种情况下生成的迭代器对象,每次调用其 __next__() 方法时,都会不带实参地调用 object。若返回的结果是 sentinel 则触发 StopIteration,否则返回调用结果。例如
使用内置 callable() 函数可判断对象是否可调用。而若类实现了 __callable__() 方法,则其实例是可调用的
i = 0 def fun1(): while True: global i i += 1 return i b = iter(fun1,4) print(next(b)) print(next(b)) print(next(b)) print(next(b))
执行结果
1 2 3 Traceback (most recent call last): File "E:\Tests.Function\demo.py", line 303, in <module> print(next(b)) StopIteration
5.2 next()--调用下一个元素
next(iterator[, default])
其中,第一个参数 iterator 为 迭代器对象。第二个参数 default 可选,用于设置 在没有下一项元素时返回的默认值/缺省值,以避免抛出异常中断程序。若不设置参数 default,且调用 next() 时无下一项元素则会触发 StopIteration 异常。例如:
i = 0 def fun1(): while True: global i i += 1 return i b = iter(fun1,3) print(next(b)) print(next(b)) print(next(b,'end'))
执行结果
1
2
3
end
三、迭代器详解
1、迭代器对象要求支持 迭代器协议 —— 对象须同时支持/实现 __iter__() 方法和 __next__() 方法。
-
实现了迭代器协议的对象,就是一个迭代器
-
所有的可迭代对象 都可以通过内置函数iter()转换为迭代器:
-
迭代器对象能够使用 内置函数next 进行迭代操作,当所有数据迭代完毕后,再使用next迭代,会抛出异常StopIteration。
-
所有的迭代器都是可迭代对象,因为迭代器协议包含了迭代协议
例子:
# 将列表转换为一个迭代器 iter_li = iter([11,22,33,44]) # 通过next对迭代器进行迭代操作,每次可以迭代出来一个数据 s1 = next(iter_li) print('s1:',s1) s2 = next(iter_li) print('s2:',s2) # 上述代码运行结果为: s1 :11 s2 :22
2、open()返回值也是一个迭代器,可以直接用for循环遍历
file = open('run.py',encoding='utf-8') print(next(file))
用法和上面说的一样,可以使用next和for循环遍历,正常情况下是使用for循环遍历的
3、如何判断一个对象是不是迭代对象、迭代器或者生成器呢?
3.1 使用instance方法,如下
from collections.abc import Iterable,Iterator,Generator l1 = [] print(isinstance(l1,Iterable)) l2 = iter(l1) print(isinstance(l2,Iterator)) print(isinstance(l1,Generator)) 执行结果: True True False
3.2 使用hasattr()判断对象有没有某个属性,如下(不能确定对象是不是生成器)
l1 = [] print(hasattr(l1,'__iter__')) l2 = iter(l1) print(hasattr(l1,'__next__')) print(hasattr(l2,'__next__')) 执行结果: True False True
四、生成器详解
生成器是一种特殊的迭代器,具备迭代器所有的特性,生成器内部不存储数据,只保存生成数据的计算规则,在存储大量数据的时候,能够节约内存的开销
python中定义生成器,一共有两种方式,一种是生成器表达式,另一种是生成器函数
4.1生成器表达式:
print((i for i in range(100))) 执行结果: <generator object <genexpr> at 0x0000021698BC5510>
4.2 生成器函数:
原理概述“
生成器函数 用于创建 生成器迭代器 (generator iterator)。
与普通函数的 区别 在于:普通函数 使用 return 返回结果,而 生成器函数 使用 yield 返回结果。
使用了 yield 表达式 的生成器函数 返回一个迭代器对象,以便于通过 for 循环或 next() 函数等逐项获取元素/数据/值/成员 (以下统称 元素)。换言之,使用了 yield 表达式的函数就是生成器函数。
例如,生成斐波那契数列前 max 个数,使用 普通函数 :
def fib_common(max): first, secend, count = 1, 1, 0 while count < max: print(first) first, secend = secend, first + secend count += 1 fib_common(8)
执行结果:
1
1
2
3
5
8
13
21
变成生成器函数也非常简单,将print(first) 更改为yield first即可
def fib_common(max): first, secend, count = 1, 1, 0 while count < max: yield first first, secend = secend, first + secend count += 1 fc = fib_common(8)
print(fc) #<generator object fib_common at 0x0000019CF2195510> for i in fc: print(i)
调用生成器函数时,需要先生成一个生成器对象,然后用for循环或者next()进行遍历
5、生成器的其他方法,send ,代码如下
#一个协程的例子 def consumer(): message = '' while True: message = yield message def product(c): c.send(None) i = 0 while i < 10: message = f'这是第{i}条消息' print(f'生成的消息为{message}') result = c.send(message) print(f'消费的消息为{result}') i+=1 c = consumer() product(c)
执行结果为:
生成的消息为这是第0条消息
消费的消息为这是第0条消息
生成的消息为这是第1条消息
消费的消息为这是第1条消息
生成的消息为这是第2条消息
消费的消息为这是第2条消息
要看懂上面的例子,关键在于要理解下面几点:
1、例子中的c.send(None)
,其功能类似于next(c)
2、n = yield r
,这里是一条语句,但要理解两个知识点,赋值语句先计算=
右边,由于右边是 yield
语句,所以yield
语句执行完以后,进入暂停,而赋值语句在下一次启动生成器的时候首先被执行;
3、send
在接受None
参数的情况下,等同于next(generator)
的功能,但send
同时也可接收其他参数,比如例子中的c.send(n)
五、 迭代器和生成器的区别
生成器属于迭代器的一种
-
1、迭代器类型是Iterator类型,生成器是Generator类型。
-
2、生成器内部不存储数据,只保存生成数据的计算规则
-
3、生成器比迭代器多了3个方法
-
send方法:在生成数据的同时,可以和生成器内部进行数据交互
-
close: 生成可以调用close方法进行关闭
-
throw: 可以在生成器内部上一次暂停的yield处引发一个指定的异常类型。生成器内部可以通过捕获的异常类型来做不同的处理
-
上面例子提到了send方法,这里再演示下close和throw方法
1、close方法,手动关闭生成器,关闭后不允许进行迭代
def fib_common(max): first, secend, count = 1, 1, 0 while count < max: yield first first, secend = secend, first + secend count += 1 fc = fib_common(8) print(next(fc)) fc.close() print(next(fc))
执行结果如下:
1 Traceback (most recent call last): File "E:\demo.py", line 335, in <module> print(next(fc)) StopIteration
2、throw()
它的实现手段是通过向生成器对象在上次被挂起处,抛出一个异常。之后会继续执行生成器对象中后面的语句,直至遇到下一个yield语句返回。如果在生成器对象方法执行完毕后,依然没有遇到yield语句,抛出StopIteration异常
def fib_common(max): first, secend, count = 1, 1, 0 while count < max: yield first first, secend = secend, first + secend count += 1 import sys fc = fib_common(8) print(next(fc)) fc.throw(AttributeError,'这是手动抛错') print(next(fc))
执行结果:
1 Traceback (most recent call last): File "E:\ABB.Ability.Tests.Function\demo.py", line 335, in <module> fc.throw(AttributeError,'这是手动抛错') File "E:\ABB.Ability.Tests.Function\demo.py", line 328, in fib_common yield first AttributeError: 这是手动抛错
五:生成器对内存使用的提升,这里举个小例子
不使用生成器
使用生成器:
可以生成器几乎不占用内存,两者之间区别还是比较大的
生成器的缺点:
只能依次往下迭代一轮,而不能回退和重复迭代。
转自:https://blog.csdn.net/qq_39478403/article/details/106028513
转自:http://testingpai.com/article/1635498887333