Python 对于容器类型数据支持逐个进行迭代处理,迭代会对所有元素按照一个逻辑进行计算操作。因此在 Python 所有数据范围内就存在类型是否是可迭代的话题。
- 为了高效完成迭代操作, python专门设计了迭代器类型, 这类数据专门用来迭代操作
- 为了高效快捷创建一个迭代器类型,Python 又有一个生成器类型成生成一个可迭代对象。
迭代器类型
迭代器(iterator)就是一个封装了迭代的对象。Python中内置的序列,如list、tuple、str、bytes、dict、set、collections.deque 等都是可迭代对象,但它们不是迭代器。迭代器可以被 next() 函数调用,并不断返回下一个值。Python 从可迭代的对象中获取迭代器。
迭代器和生成器都是为了惰性求值(lazy evaluation),避免浪费内存空间,实现高效处理大量数据。
- 在 Python 3中,生成器有广泛的用途,所有生成器都是迭代器,因为生成器完全实现了迭代器接口。
- 迭代器用于从集合中取出元素
- 生成器用于"凭空"生成元素 。
迭代器用于支持:
- for 循环
- 构建和扩展集合类型
- 逐行遍历文本文件
- 列表推导、字典推导和集合推导
- 元组拆包
- 调用函数时,使用 * 拆包实参
判断对象是否为迭代器:
from collections import abc
isinstance([1,2,3], abc.Iterator)
# False
isinstance((1,2,3), abc.Iterator)
# False
isinstance({'name': 'lily', 'age': 18}, abc.Iterator)
# False
isinstance({1, 2, 3}, abc.Iterator)
# False
isinstance('abc', abc.Iterator)
# False
isinstance(123, abc.Iterator)
# False
# 生成器表达式,见下文
isinstance((x*2 for x in range(5)), abc.Iterator)
# True
可以看到,常见的类型,它们不是迭代器类型.
那, 如何让一个对象成为迭代器类型呢?
- 要转为迭代器类型可以用内置函数 iter(),要使用 内置函数 iter() 需要对此对象支持此函数。
- 对于容器类型,需要实现 container.__iter__ () 方法来提供可扩展的支持,用来支持使用内置函数 iter() 将其转为 迭代器类型,Python 大多内置的容器已经实现了这个方法。
from collections import abc
it = iter('abc')
it
# <str_iterator at 0x7f8482595e70>
isinstance(it, abc.Iterator)
# True
- 对于其他类型对象,从技术上讲, python迭代器对象必须支持迭代器协议。所谓迭代器协议,就是要求一个迭代器必须要实现如下两个方法:
- iterator.__iter__():返回迭代器对象本身
- iterator.__next__():从容器中返回下一个项
- 一旦迭代器的 __next__() 方法引发了 StopIteration,它必须一直对后续调用引发同样的异常。 不遵循此行为特性的实现将无法正常使用。
- 也就是说,一个对象只要支持上面两个方法,就是迭代器。
class PowTwo:
"""Class to implement an iterator
of powers of two 二的幂"""
def __init__(self, max=0):
self.max = max
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n <= self.max:
result = 2 ** self.n
self.n += 1
return result
else:
raise StopIteration
PowTwo(3)
# <__main__.PowTwo at 0x7f84823bdf60>
[*PowTwo(3)]
# [1, 2, 4, 8]
n = PowTwo(3)
it = iter(n)
from collections import abc
isinstance(it, abc.Iterator)
# True
next(it) # 1
next(it) # 2
next(it) # 4
next(it) # 8
next(it) # StopIteration
此外,还可以实现一个无限的迭代器,即元素是无限的,永远迭代不完。以下我们实现是一个无限偶数迭代器:
class Even:
"""Infinite iterator to return all
Even numbers"""
def __iter__(self):
self.num = 2
return self
def __next__(self):
num = self.num
self.num += 2
return num
# 注意!这个看就行,别执行,会死循环
[*Even()]
e = iter(Even())
next(e) # 2
next(e) # 4
next(e) # 6
next(e) # 8
next(e) # 10
# ...
内容函数 iter() 也可以生成无限迭代器,在传入两个参数的形式中,调用 callable 直到它返回 sentinel(哨兵,这里设置一个它不可能到达的值)。如:
int()
# 0
inf = iter(int,1)
next(inf) # 0
next(inf) # 0
在处理此类迭代器时,我们必须小心,注意要设置终止条件,否则非常容易造成死循环。
使用迭代器的优点是可以节省资源,比如上例中,我们可以在不将整个数字系统存储在内存中的情况下获得所有偶数。我们可以在有限的内存中拥有无限的内容项(理论上)。
总结下,迭代器的特点:
- 惰性:迭代器并不是把所有的元素提前计算出来,而是在需要的时候才计算返回。
- 支持无限个元素:它创建是一个规则,由于是惰性的,你永远可能不会把所有元素全部使用,而列表等容器没法容纳无限个元素的
- 节省空间:迭代器惰性的特点,存储的是规则算法,因此不会占用太多内存
- 迭代器同时也是可迭代对象
- 迭代器遍历完一次就不能从头开始了,用完即销毁,对项的使用是「一次性」的
itertools 库
Python 内置库 itertools 中的大多数函数是返回各种迭代器对象,如果自己去实现同样的功能,代码量会非常大,而在运行效率上反而更低,因此,我们很有必要了解一下这个标准库。