迭代器
浏览 766
课文
什么是迭代器
我们先来看一个最简单的迭代器
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
在这里做了两件事,
- 通过
iter(result)
获取了一个具有__next__
方法的迭代器假设为x
。 - 循环调用
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的大小。当我们要执行人类清除计划时会发现无法把这个文件一次读进内存。这时我们就可以通过迭代器的方法每次仅读取部分内容。
假如 我的很大
的文件长这样:
那我们可以通过一次读取一行的方式配合迭代器来载入数据,防止内存爆掉。
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)
评论
暂无评论