迭代器

浏览 109

课文

## 什么是迭代器 我们先来看一个最简单的迭代器 ```python 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) ``` ```output 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` 的功能。 ```python ... 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__`是可以分别在两个类中实现的。 ```python 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) ``` ```output 1 2 3 4 5 ``` python 中的列表、集合、元组、字典也都是一个 `可迭代对象`,这表明这些类型可以直接作用于 `for in` 循环。 ```python obj = [1, 2, 3] print('是否有 __iter__ 方法:', hasattr(obj, '__iter__')) print('是否有 __next__ 方法:', hasattr(obj, '__next__')) print(dir(obj)) ``` ```output 是否有 __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` 方法获取一个 `迭代器` 对象。 ```python obj = [1, 2, 3] new_obj = iter(obj) print('是否有 __iter__ 方法:', hasattr(new_obj, '__iter__')) print('是否有 __next__ 方法:', hasattr(new_obj, '__next__')) print(dir(new_obj)) ``` ```output 是否有 __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` 判断即可。 ```python from collections.abc import Iterable, Iterator obj = [1, 2, 3] print('是否可迭代对象', isinstance(obj, Iterable)) print('是否迭代器', isinstance(obj, Iterator)) ``` ```output 是否可迭代对象 True 是否迭代器 False ``` ## 使用迭代器的场景 需要注意的是,以下的应用场景不使用迭代器也是可以实现的,绝大部分情况下使用迭代器是使代码更易读更易于重构,直接像对待数组一样使用 `for in` 来操作数据。 ### 节省内存 假如我们有一份文件存着所有人类的姓名,这个文件可能有几百TB的大小。当我们要执行人类清除计划时会发现无法把这个文件一次读进内存。这时我们就可以通过迭代器的方法每次仅读取部分内容。 假如 `我的很大` 的文件长这样: ![image](https://qiniu.3yya.com/479c5174f46903665dc26f1b97577bb0/479c5174f46903665dc26f1b97577bb0.png) 那我们可以通过一次读取一行的方式配合迭代器来载入数据,防止内存爆掉。 ```python 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() ``` ```output 清除人类: 王小明 清除人类: 鸣人 清除人类: 柯南 清除人类: 王路飞 ``` ### 流式处理数据 比如我们要从网络中一直读取数据并处理,由于数据是从火星发送的,每次耗时较长。如果我们等待数据读取完全再进行处理,既浪费了等待的时间没有处理数据,用户体验上也不友好。 这会用迭代器就能边读取数据,边处理数据,边展示数据。 ```python 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) ``` ```output 读取到数据并处理: 火星照片 读取到数据并处理: 火星五星红旗照片 读取到数据并处理: 火星人照片 ``` ### 无限的数据 有些数据是无限的,比如所有的正奇数,这会可以用迭代器来表示。 ```python 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

课程目录

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

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