一、什么是迭代器


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

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)  

while True:
    try:
        n = next(result_iter)  
        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 来操作数据。

1、节省内存

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

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

python中迭代器能回来吗 python迭代器详解_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()


清除人类: 王小明
清除人类: 鸣人    
清除人类: 柯南    
清除人类: 王路飞

2、流式处理数据

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

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

import time

class Mars:
    
    _data = ['火星照片', '火星五星红旗照片', '火星人照片', 'end']
    _index = 0

    def __iter__(self):
        return self

    def read_data(self):
        
        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)


读取到数据并处理: 火星照片
读取到数据并处理: 火星五星红旗照片
读取到数据并处理: 火星人照片

3、无限的数据

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

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)