2.3 - 可迭代对象、迭代器和生成器

2.3.1 判断 可迭代对象和迭代器和生成器

  可迭代对象不一定是迭代器,但是迭代器一定是可迭代对象。

  可迭代对象:str;tuple;list;dict;set;forzenset;range。

  返回一个迭代器:map;filter;reversed;zip;enumerate。

真正准确判断是否是迭代器的方法

from typing import Iterable, Iterator

lst = [1, 2, 3]

print(isinstance(lst, Iterator))  # 判断实例对象lst是否是迭代器 
print(issubclass(list, Iterator))  # 判断某个类实例化之后是否是 迭代器

但是:上述的两种判断方法并不准确,因为 isinstance和issubclass 在判断是否是可迭代对象的时候,只判断是否支持迭代协议(__iter__()),如果对象只实现了序列协议而没有实现迭代协议,尽管这个对象依旧是可迭代对象,但是会被判为False。

真正准确的判断是否是可迭代对象的方法

# 判断 类或实例 是否是可迭代对象

lst = [1, 2, 3]

class A:
    pass


print('__iter__' in dir(lst) or '__getitem__' in dir(lst))  # 判断实例对象是否是可迭代对象
print('__iter__' in dir(A) or '__getitem__' in dir(A))  # 判断类是否是可迭代对象

 

2.3.2 可迭代对象

  需要满足两个条件之一:
  1)支持迭代协议(包含 __iter__() 方法)

  2)支持序列协议(包含 __getitem__(self, item) 方法,且数字参数从0开始),该协议支持了  列表的索引、切片功能。

 

2.3.3 迭代器

  必须同时满足下列两个条件:

  1)实现 __iter__() 方法  【这也是 迭代器一定是可迭代对象的原因。】

  2)实现 __next__() 方法

 

2.3.4 可迭代对象和迭代器 的迭代过程

# 迭代列表
lst = [3,5,8,6]
for i in lst:
    print(i)

如上述案例所示: 

对一个可迭代对象: 

  当执行for循环的时候,首先判断对象是不是 Iterable,如果不是则抛出 TypeError 异常。如果是,则会优先自动触发 __iter__() 方法,如果没有实现该方法,才会触发序列协议 __getitem__() 方法。

  当执行 __getitem__(self,item) 方法的时候:将可迭代对象自身传递给self,下标索引 item从0开始,每次循环 item 的值加1,并且在上述案例中,每次循环返回的值都会赋值给 迭代元素 i。直到超出索引范围,抛出 StopIteration 异常,for循环捕获异常之后,迭代结束。

  当执行 __iter__方法的时候:将可迭代对象自身传递给self,然后 __iter__方法会返回一个 迭代器对象,之后每次循环都会自动调用迭代器的 __next__()  方法,并将next()方法返回的值赋值给 迭代元素i。直到 __next__ 方法抛出 StopIteration 异常,for循环捕获异常之后,迭代结束。

对一个迭代器

  因为迭代器实现了迭代协议__iter__,所以 迭代器本身也是可以迭代的。当一个迭代器进行迭代的时候,同样会首先自动触发 __iter__ 方法,这时候__iter__ 方法会返回自身,之后调用自身的next方法执行后续过程。

 

2.3.5 生成器(特殊的迭代器)

  通过列表生成式,我们可以创建一个列表:lst = [ x*x for x in range(5) ],但是,受到内存限制,列表容量肯定是有限的。如果列表元素可以按照某种算法推算出来,那我们可以在循环的过程中一边遍历一边推算后续的元素,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

创建生成器的第一种方式:()

# 创建生成器的第一种方式,适用于 元素的推算逻辑比较简单的情况

# 通过 () ,列表生成式使用的是 []
g = (x*x for x in range(5))

for i in g:
    print(i) 

  列表生成式使用 [ ] 创建一个列表,生成器使用 ()创建一个生成器。

创建生成器的第二种方式:yield

# 创建生成器的第二种方式,适用于 元素的推算逻辑比较复杂的情况

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield a
        a, b = b, a+b
        n = n + 1

  如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator。

generator函数和普通函数的区别

  普通函数是顺序执行,遇到return语句或者最后一行函数语句返回;generator函数定义完成之后,在每次调用 next() 的时候执行,遇到 yield 语句返回,然后程序被挂起,直到再次调用 next() 函数时,从上次返回的 yield 语句处继续往下执行。

:每调用一次generator函数就会新建一个生成器,不同的生成器之间互相独立,各自迭代各自的,互不影响。

  生成器作为另一个函数的参数的时候,可以将生成器的表达式(创建方式一)直接传入作为参数,不必用 ()括起来。

# 生成器作为函数参数

sum( x*x for x in range(3) )  # 5

 

 

例:分下如下程序的结果:

def g(a, b):
    print(1)
    yield a + b
    print(2)
    yield a - b
    print(3)


if __name__ == "__main__":
    for x in g(6, 2):
        print(x)

打印输出:1 8 2 2 3

 

posted @ 2024-01-22 22:40  橘子葡萄火龙果  阅读(6)  评论(0编辑  收藏  举报