python 可迭代对象、迭代器、生成器详解

学习目标

  • 学习迭代器是为了学习生成器,学习生成器是为了学习协程,协程就是实现多任务的一种方式

  • 知道什么是可迭代对象,能够使用isinstance检测对象是否可迭代

  • 知道使用iter函数可以获取可迭代对象的迭代器

  • 知道使用next函数可以获得迭代器数据

 

一、可迭代对象、迭代器、生成器三者之间的联系

先看一张图:

官网文档地址

从上图中可以看出:

Iterable(可迭代对象)、Iterator(迭代器)、genetator(生成器)关系如下:

  • 可迭代对象 有 __iter__方法
  • 迭代器继承了可迭代对象,有 __iter__、__next__ 这两个方法
  • 生成器又继承了迭代器,有 send、close、 __iter__、__next__ 等方法

 

 二、可迭代对象详解

1、介绍:

可迭代对象就是能够通过for循环迭代,逐一返回其成员项的对象称为可迭代对象, python中可迭代对象包括

  • 所有序列类型 :如 list、 str 和 tuplerange
  • 非序列类型: dictset文件对象
  • 实现了 __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


posted @ 2022-07-07 18:28  上官夏洛特  阅读(619)  评论(0编辑  收藏  举报