python-迭代器

 

迭代的概念

使用for循环遍历取值的过程叫做迭代,比如:使用for循环遍历列表获取值的过程

# Python 中的迭代
for value in [2, 3, 4]:
    print(value)
复制代码

1.2 可迭代对象

标准概念:在类里面定义__iter__方法,并使用该类创建的对象就是可迭代对象

简单记忆:使用for循环遍历取值的对象叫做可迭代对象, 比如:列表、元组、字典、集合、range、字符串


 
# 常见的可迭代对象
iterables = [
    "123",          # 字符串
    [1,2,3],        # 列表
    (1,2,3),        # 元组
    {1:'a',2:'b'},  # 字典
    {1,2,3}         # 集合
]

 

可迭代对象之间共同的特点

利用python的内省能力 使用dir()函数获取python对象的属性 

# 常见的可迭代对象
iterables = [
    "123",          # 字符串
    [1,2,3],        # 列表
    (1,2,3),        # 元组
    {1:'a',2:'b'},  # 字典
    {1,2,3}         # 集合
]

# for iterable in iterables:
#     print(type(iterable))
#     for x in iterable:
#         print(x,end=',')
#     print('')


def common_attrs(*objs):
    """
    计算对象之间的共同属性
    :param objs:
    :return:
    """
    assert  len(objs) >0
    attrs = set(dir(objs[0]))
    for obj in objs[1:]:
        print(obj)
        attrs &= set(dir(obj))  # 取交集
    attrs -= set(dir(object))   # 剔除基础对象的属性
    return attrs

iterable_common_attrs=common_attrs(*iterables)
print(iterable_common_attrs)

 输出结果如下

[1, 2, 3]
(1, 2, 3)
{1: 'a', 2: 'b'}
{1, 2, 3}
{'__contains__', '__iter__', '__len__'}

因为上面都是容器类型的可迭代对象,所以有长度 和是否包含属性

在加入文件类型的可以

f = open('D:\\PycharmProject\\StudentSenior\\file\\demo1.xlsx','r')

# d=common_attrs(f)

iterable_common_attrs &= set(dir(f))
print(iterable_common_attrs)

输出结果如下

{'__iter__'}

现在我们得到可迭代对象的一个唯一接口 __iter__

这种双下划线的__iter__ 方法 我们需要用相应的内置函数去调用 iter()

for iterable in iterables:
    print(iter(iterable))

输出如下

<str_iterator object at 0x00000184F4C81F70>
<list_iterator object at 0x00000184F4C81F70>
<tuple_iterator object at 0x00000184F4C81F70>
<dict_keyiterator object at 0x00000184F4B76400>
<set_iterator object at 0x00000184F4B13940>
<_io.TextIOWrapper name='D:\\PycharmProject\\StudentSenior\\file\\demo1.xlsx' mode='r' encoding='cp936'>

我们会 发现生成了iterator object对象,这也是 迭代器

 

 

迭代器共同的属性

# 由可迭代对象列表得到相应的迭代器列表
iterator = [iter(iterable) for iterable in iterables]

# 计算迭代器的共同属性
iterator_common_attrs = common_attrs(*iterator)

print(iterator_common_attrs)

输入结果如下

{'__iter__', '__next__'}

证明迭代器有两个接口 __iter__  和 __next__

 

 

对一个迭代器迭代

actions = ['点赞','投币','收藏']  # 可迭代对象

actions_iterator = iter(actions)   # 构建迭代器

while 1:
    action = next(actions_iterator)  # 运行三次
    print(action)

结果如下:

Traceback (most recent call last):
  File "D:\PycharmProject\StudentSenior\test_openpyxl\test05.py", line 8, in <module>
    action = next(actions_iterator)  # 运行三次
StopIteration
点赞
投币
收藏

 我们可以发现 在迭代完最后一个元素后在去迭代 发生了一个异常StopIteration,也就是迭代终止的意思

至此我们已经知道了迭代的三个关键步骤:

  调用iter(iterable)来构建迭代器

  (多次)调用next(iterator)来获取值

  最后捕获StopIteration异常来判断迭代结束

改进如下

# 用while 循环模拟for循环迭代
# 创建迭代器
iterator = iter(actions)   # 对应 可迭代对象的__iterator__ 方法
while 1:
    try:
        # 通过迭代器获取下一个对象
        print(next(iterator))  # 对应迭代器的 __next__ 方法
    except StopIteration:      # 捕获异常来判断结束
        # 迭代结束
        break

 

迭代器的 __iter__ 方法作用在哪里呢?

 

自定义一个迭代器

迭代器的的基本功能:

  初始化时要传入可迭代对象,这样才能知道去那取数据

  要初始化迭代进度

  每次迭代时,即每次调用__next__() 方法时:

    如果仍有元素可供迭代,则返回本轮迭代的元素,同时更新当前迭代进度

    如果已无元素可供返回,则迭代结束,抛出StopIteration异常 

再添加一点额外的逻辑:

  设置一个黑名单,如果当前元素在黑名单内,则跳过

  将某些符合条件的数据*2之后再返回


BLACK_LIST = ['白嫖','取关']


class SuzhiIterator:

    def __init__(self,actions):
        self.actions =actions
        self.index = 0                   # 初始化索引下标


    def __next__(self):

        while self.index < len(self.actions):
            action = self.actions[self.index]
            self.index +=1
            if action in BLACK_LIST:
                continue
            elif '币' in action:
                return action*2
            else:
                return action
        raise StopIteration


actions = ['点赞','投币','取关']
sz_iterator = SuzhiIterator(actions)

while True:
    try:
        print(next(sz_iterator))
    except StopIteration:
        break
        

这个可以放在while 循环里面去 迭代,但是不能放在for 循环去迭代,

会报 是一个不可迭代对象

TypeError: 'SuzhiIterator' object is not iterable

那这时候我们可能想到要不要额外定义一个类 去实现__iter__

# 一个没有存在意义的可迭代对象
class SuzhiActions:

    def __init__(self,actions):
        self.actions = actions

    def __iter__(self):
        return SuzhiIterator(self.actions)

for i in SuzhiActions(actions):
    print(i)

我们这样做太过于冗余

我们正确的做法是让迭代器自己实现

class SuzhiIterator:

    def __init__(self,actions):
        self.actions =actions
        self.index = 0                   # 初始化索引下标


    def __next__(self):

        while self.index < len(self.actions):
            action = self.actions[self.index]
            self.index +=1
            if action in BLACK_LIST:
                continue
            elif '币' in action:
                return action*2
            else:
                return action
        raise StopIteration
    
    def __iter__(self):
        return self

在这里 __iter__ 接口的含义就是要返回一个迭代器,那现在__iter__ 已经在这个迭代器里面了,而他这个对象自己就是一个迭代器,所以返回这个对象就可以了

迭代器协议

在python文档中明确指出了,迭代器必须同时实现 __next__ 和 __iter__ 两个方法,这称之为 迭代器协议

根据这个协议,迭代器必须是可迭代的,换言之,迭代器 是一种 可迭代对象

缺少了 __iter__ 方法的迭代器是不完整的,不符合迭代器协议的要求

所有迭代器的 __iter__ 方法都只要千篇一律的return shlf 即可

 

迭代器的意义

浅层的意义

  统一通过next() 方法获取数据,可以屏蔽底层不同的数据读取方式,简化编程

  容器类的数据结构只关心数据的静态存储,每一次迭代都需要额外的迭代器对象专门负责记录迭代过程中的状态信息

按照这个思路,我们很容易形成这样的认知:

迭代器就是为了让数据结构能够快捷地遍历而定义的辅助对象,但是别忘了,如果只是在for循环中使用,是无需用的迭代器的__iter__方法的

 

深层的意义

为什么说迭代器的__iter__ 方法是点晴之笔!

现在有两种可迭代对象

容器类型的:
    列表、元组、字典等
    只有__iter__接口
    静态的数据
    需要额外的迭代器支持
    支持多次迭代
迭代器类型
    文件、StringIO等
    同时实现__iter__ 和 __next__接口
    动态的
    只能迭代一次
# 针对容器类可迭代对象的循环

for x in iterable:  # 背后操作:【可迭代对象】 -> 【迭代器】
    pass

此种情况下,只有迭代对象在前台露脸,而迭代器是在背后使用默认的方式 悄悄构建的,没有存在感 并且生命周期是和循环操作本身绑定在一起的

 

而一旦迭代器实现了 __iter__ 方法:

# 针对迭代器的循环
interator = iter(iterable)   # 迭代器的构建过程远离循环而存在

for x in interator:  # 背后操作: 【迭代器】 -> 【迭代器】 (self)
    pass

现在整个迭代过程只需要迭代器就够了

迭代器不光是从后台走向了前台,而且直接让可迭代对象远离了循环

 

现在 迭代器的构建是在明面上单独完成的,和当前循环操作解耦了。

于是乎:

  •   一个可迭代对象可以构建出任意多个不同的迭代器
  •   一种迭代器可以应用于任意多个可迭代对象(包括其他迭代器)

 

应用场景

数据管道(数据流)

如果迭代器不可迭代

【for循环】 <- 【迭代器(隐藏)】 <- 【可迭代对象】

现在,迭代器可以任意的嵌套连接:

【for循环】 <- 【迭代器】 <- 【迭代器】<- ....<- 【迭代器】<- 【迭代器】<- 【可迭代对象】

很多个迭代器串联起来,形成一个处理数据的管道,或者称为数据流

在这个管道中每一次只通过一份数据,避免了一次性加载所有数据

迭代器也不仅仅只是按顺序返回数据那么简单了,它开始承担处理数据的责任

  例如:SuzhiIterator实际实现了部分过滤器和放大器的功能。

当通过迭代器获取数据的时候,远离了数据存储,渐渐地开始不关心数据到底是怎么存储的

from random import random

class Random:
    
    def __iter__(self):
        return self
    
    def __next__(self):
        return random()

这个迭代器不但不需要存储数据,甚至连StopIteration 都不用管

它可以无穷尽的迭代下去,每次数据都是实时产生的,并不占用内存空间

有经验的应该能看出 ,这不就是【生成器】嘛

虽然该迭代器是名副其实的数据生成器,但是生成器(Generator)在python中特指包含yield的函数对象

 

附录:没有定义 __iter__ 的可迭代对象

并不是所有的可迭代对象都必须定义有__iter__ 方法。

如果一个对象没有 __iter__ 方法 ,但是定义了 __getitem__ 方法,同样是可以迭代的

posted @ 2022-11-19 17:04  钟鼎山林  阅读(173)  评论(0编辑  收藏  举报