迭代器

浏览 373

课文

什么是迭代器

我们先来看一个最简单的迭代器

class Positive:
    def __init__(self, limit):
        self.limit = limit
        self.n = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.n += 1
        if self.n <= self.limit:
            return self.n
        raise StopIteration

result = Positive(5)

for n in result:
    print(n)
1
2
3
4
5

这个迭代器的功能是返回小于等于指定值的 正整数

代码里的 result 变量同时是 可迭代对象迭代器

  • 可迭代对象:具有 __iter__ 方法,通过调用 iter(x) (等价于调用 x.iter())将会返回一个迭代器。
  • 迭代器:具有 __next__ 方法,通过调用 next(x) (等价于调用 x.next())每次返回一个结果, raise StopIteration 来结束迭代。

for n in result 在这里做了两件事,

  1. 通过iter(result) 获取了一个具有 __next__ 方法的迭代器假设为 x
  2. 循环调用 next(x) 获取返回值赋值到 n 变量上,直到捕获 StopIteration 异常。

因此我们可以手动调用 next 来实现 for in 的功能。

...
result = Positive(5)

result_iter = iter(result)  # 等价于 result.__iter__()

while True:
    try:
        n = next(result_iter)  # 等价于 result_iter.__next__()
        print(n)
    except StopIteration:
        break

很多网上的资料会把 __iter____next__ 定义在一个类里面,使这个类实例化出来的对象同时是 可迭代对象迭代器

然而__iter____next__是可以分别在两个类中实现的。

class Positive:
    def __init__(self, limit):
        self.limit = limit

    def __iter__(self):
        return Part(self.limit)

class Part:
    def __init__(self, limit):
        self.limit = limit
        self.n = 0

    def __next__(self):
        self.n += 1
        if self.n <= self.limit:
            return self.n
        raise StopIteration

result = Positive(5)

for n in result:
    print(n)
1
2
3
4
5

python 中的列表、集合、元组、字典也都是一个 可迭代对象,这表明这些类型可以直接作用于 for in 循环。

obj = [1, 2, 3]

print('是否有 __iter__ 方法:', hasattr(obj, '__iter__'))
print('是否有 __next__ 方法:', hasattr(obj, '__next__'))

print(dir(obj))
是否有 __iter__ 方法: True
是否有 __next__ 方法: False
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

我们可以看到列表有 __iter__ 方法,代表是一个可迭代对象,没有 __next__ 代表其不是一个迭代器。

可以使用 iter 方法获取一个 迭代器 对象。

obj = [1, 2, 3]

new_obj = iter(obj)

print('是否有 __iter__ 方法:', hasattr(new_obj, '__iter__'))
print('是否有 __next__ 方法:', hasattr(new_obj, '__next__'))

print(dir(new_obj))
是否有 __iter__ 方法: True
是否有 __next__ 方法: True
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']

当然判断 可迭代对象迭代器 还有个更简单的方法,直接引入对应的类型用 isinstance 判断即可。

from collections.abc import Iterable, Iterator

obj = [1, 2, 3]

print('是否可迭代对象', isinstance(obj, Iterable))
print('是否迭代器', isinstance(obj, Iterator))
是否可迭代对象 True
是否迭代器 False

使用迭代器的场景

需要注意的是,以下的应用场景不使用迭代器也是可以实现的,绝大部分情况下使用迭代器是使代码更易读更易于重构,直接像对待数组一样使用 for in 来操作数据。

节省内存

假如我们有一份文件存着所有人类的姓名,这个文件可能有几百TB的大小。当我们要执行人类清除计划时会发现无法把这个文件一次读进内存。这时我们就可以通过迭代器的方法每次仅读取部分内容。

假如 我的很大 的文件长这样:

image

那我们可以通过一次读取一行的方式配合迭代器来载入数据,防止内存爆掉。

class Names:
    def __init__(self, path):
        self.f = open(path, 'r', encoding='utf-8')

    def __iter__(self):
        return self

    def __next__(self):
        name = self.f.readline()
        if name == '':
            raise StopIteration

        return name.strip()  # 去除换行符

    def close(self):
        self.f.close()

def kill(name):
    print('清除人类:', name)

names = Names('names.txt')

for name in names:
    kill(name)

names.close()
清除人类: 王小明
清除人类: 鸣人    
清除人类: 柯南    
清除人类: 王路飞

流式处理数据

比如我们要从网络中一直读取数据并处理,由于数据是从火星发送的,每次耗时较长。如果我们等待数据读取完全再进行处理,既浪费了等待的时间没有处理数据,用户体验上也不友好。

这会用迭代器就能边读取数据,边处理数据,边展示数据。

import time

class Mars:
    # 虚拟的火星数据
    _data = ['火星照片', '火星五星红旗照片', '火星人照片', 'end']
    _index = 0

    def __iter__(self):
        return self

    def read_data(self):
        # 假设每次接收数据耗时 1 秒
        time.sleep(1)
        data = self._data[self._index]
        self._index += 1
        return data

    def __next__(self):
        data = self.read_data()
        if data == 'end':
            raise StopIteration
        return data

mars = Mars()

for data in mars:
    print('读取到数据并处理:', data)
读取到数据并处理: 火星照片
读取到数据并处理: 火星五星红旗照片
读取到数据并处理: 火星人照片

无限的数据

有些数据是无限的,比如所有的正奇数,这会可以用迭代器来表示。

class Odd:
    def __init__(self):
        self.n = 1

    def __iter__(self):
        return self

    def __next__(self):
        value = self.n
        self.n += 2
        return value

for n in Odd():
    print(n)

评论

登录参与讨论

暂无评论

共 0 条
  • 1
前往
  • 1

课程目录

第一课:命名空间与作用域 第二课:闭包 第三课:装饰器 第四课:迭代器 第五课:生成器

学习遇到困难?微信扫码进入社群与小伙伴一起交流讨论。