可迭代对象

python 中涉及类似 for, in 等各种容器操作,这些操作通常都要求他们是可迭代的类型。只要定义了 __iter____getitem__ 就是可迭代对象,可以通过 iter() 来判断是否是可迭代对象。

from typing import Any, Iterable


class ListStartWith1:
    def __init__(self, n: int):
        self.__n = n
        self.__list = [i + 1 for i in range(n)]

    def __getitem__(self, index: int):
        if index > self.__n or index < 1:
            raise IndexError
        return self.__list[index - 1]

    def __len__(self):
        return self.__n


l = ListStartWith1(3)
v = l[1]
print(iter(l))
print(isinstance(l, Iterable))

从上面的代码可以看出 isinstance(obj, Iterable) 的方式只能检查是否有 __iter__ 方法,并不能完全检查出是否是可迭代对象,更可靠的方式是通过 iter() 来判断是否是可迭代对象。iter(obj) 返回一个迭代器,因此可迭代类型的判断依据是能否返回迭代器。

所有的序列类型 Sequence 都是可迭代对象,如 list, tuple, str 等都是可迭代的

迭代器

可迭代对象使用 iter() 方法会返回一个迭代器 iterator,迭代器需要实现迭代器协议 [^1]

迭代器协议

  • iterator.__iter__()
    返回 iterator 对象本身。 这是同时允许容器和迭代器配合 for 和 in 语句使用所必须的。
  • iterator.__next__()
    iterator 中返回下一项。 如果已经没有可返回的项,则会引发 StopIteration 异常。
    一旦迭代器的 __next__() 方法引发了 StopIteration,它必须一直对后续调用引发同样的异常。 不遵循此行为特性的实现将无法正常使用。

迭代器

比如实现一个斐波那契数列的迭代器

class Fib:
    def __init__(self, n: int):
        self.__index = 0
        self.__len = n
        self.__cur = 0
        self.__later = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.__index < self.__len:
            val = self.__cur
            self.__cur, self.__later = (self.__later, self.__later + self.__cur)
            self.__index += 1
            return (self.__index - 1, val)
        else:
            raise StopIteration

从上面的代码可以看出,迭代器采用的是一种“懒加载”的方式,他没有一次性把所有东西都载入内存,而是需要的时候再算出来。

生成器

可以看到直接定义一个自己的迭代器还是挺麻烦的,为了简化,可以使用生成器。生成器是一种特殊的迭代器,有两种定义方式。

生成器函数:生成器函数通过 yield 来返回每次需要的值,然后函数会在此临时暂停处理,记住当前的执行位置,当恢复运行时,会从离开的位置继续执行

def fib(n: int):
    print("start")
    start = 0
    cur, later = 0, 1
    while start < n:
        start += 1
        yield (start - 1, cur)
        cur, later = later, cur + later

生成器表达式:这种写法更简单,写法类似与列表推导式,将列表推导式中的 [] 换作 () 即可

f = (i * i for i in range(10))

可迭代对象、迭代器和生成器的关系可以用下图表示

迭代器、生成器、可迭代对象之间的关系

推荐阅读

  1. 创建数据处理管道
  2. 带有外部状态的生成器函数
  3. 同时迭代多个序列

References

[^1]:迭代器类型 Python 3.12.0 文档
[^2]:Python迭代器和生成器详解 - 知乎