迭代简介:
本节讲述python的可迭代的对象、迭代器和生成器。迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。
与 Lisp(Paul Graham 最喜欢的语言)不同,Python 没有宏,因此为了抽象出迭代器模式,需要改动语言本身。为此,Python 2.2(2001 年)加入了 yield 关键字。 这个关键字用于构建生成器(generator),其作用与迭代器一样。
所有生成器都是迭代器,因为生成器完全实现了迭代器接口。不过,根据《设计模式:可复用面向对象软件的基础》一书的定义,迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数列中的数有无穷个,在一个集合里放不下。不过要知道,在 Python 社区中,大多数时候都把迭代器和生成器视作同一概念。
在 Python 3 中,生成器有广泛的用途。现在,即使是内置的 range() 函数也返回一个类似生成器的对象,而以前则返回完整的列表。如果一定要让 range() 函数返回列表,那么必须明确指明(例如,list(range(100)))。
在 Python 中,所有集合都可以迭代。在 Python 语言内部,迭代器用于支持:
1、for 循环
2、构建和扩展集合类型
3、逐行遍历文本文件
4、列表推导、字典推导和集合推导
5、元组拆包
6、调用函数时,使用 * 拆包实参
示例:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __getitem__(self, index):
return self.words[index]
def __len__(self):
return len(self.words)
def __repr__(self):
# reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
return 'Sentence(%s)' % reprlib.repr(self.text)
if __name__ == '__main__':
s = Sentence('"The time has come," the Walrus said,')
print(s)
for word in s:
print(word)
# 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代的类型。
print(list(s))
序列可以迭代的原因:iter
函数
解释器需要迭代对象 x
时,会自动调用 iter(x)
。
内置的 iter
函数有以下作用。
(1) 检查对象是否实现了 __iter__
方法,如果实现了就调用它,获取一个迭代器。
(2) 如果没有实现 __iter__
方法,但是实现了 __getitem__
方法,Python 会创建一个迭代 器,尝试按顺序(从索引 0 开始)获取元素。
(3) 如果尝试失败,Python 抛出 TypeError
异常,通常会提示“C object is not iterable”
( C 对象不可迭代),其中 C 是目标对象所属的类。
任何Python 序列都可迭代的原因是,它们都实现了 __getitem__
方法。其实,标准的序 列也都实现了 __iter__
方法,因此你也应该这么做。之所以对 __getitem__
方法做特殊处 理,是为了向后兼容,而未来可能不会再这么做。
而鸭子类型(duck typing)的极端形式:不仅要实现特殊的 __iter__
方 法,还要实现 __getitem__
方法,而且 __getitem__
方法的参数是从 0 开始的整数(int), 这样才认为对象是可迭代的。
在白鹅类型(goose-typing)理论中,可迭代对象的定义简单一些,不过没那么灵活:如果 实现了 __iter__
方法,那么就认为对象是可迭代的。此时,不需要创建子类,也不用注册, 因为 abc.Iterable
类实现了 __subclasshook__
方法。如下面例子可以说明:
不过要注意,虽然前面定义的Sentence
类是可以迭代的,但却无法通过issubclass
,(Sentence, abc.Iterable
) 测试。
从 Python 3.4 开始,检查对象 x
能否迭代,最准确的方法是:调用 iter(x)
函数,如果不可迭代,再处理TypeError
异常。这比使用isinstance(x, abc.Iterable)
更准确,因为iter(x)
函数会考虑到遗留的__getitem__
方 法,而 abc.Iterable
类则不考虑。
迭代对象之前显式检查对象是否可迭代或许没必要,毕竟尝试迭代不可迭代的对象时, Python
抛出的异常信息很明确:TypeError: 'C' object is not iterable
。如果除了抛出 TypeError
异常之外还要做进一步的处理,可以使用 try/except 块,而无需显式检查。如果要保存对象,等以后再迭代,或许可以显式检查,因为这种情况可能需要尽早捕获错误。
可迭代的对象与迭代器的对比
可迭代的对象:
使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__
方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__
方法,而且其参 数是从零开始的索引,这种对象也可以迭代。
我们要明确可迭代的对象和迭代器之间的关系:Python
从可迭代的对象中获取迭代器。
下面是一个简单的 for
循环,迭代一个字符串。这里,字符串 ‘ABC’ 是可迭代的对象。背 后是有迭代器的,只不过我们看不到:
如果没有 for 语句,不得不使用 while 循环模拟,要像下面这样写:
1、 使用可迭代的对象构建迭代器 it。
2、 不断在迭代器上调用 next 函数,获取下一个字符。
3、 如果没有字符了,迭代器会抛出 StopIteration
异常。
4、 释放对 it 的引用,即废弃迭代器对象。
StopIteration
异常表明迭代器到头了。Python 语言内部会处理 for 循环和其他迭代上下 文(如列表推导、元组拆包,等等)中的 StopIteration
异常。
__next__
返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。
__iter__
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。
这个接口在 collections.abc.Iterator
抽象基类中制定。这个类定义了 __next__
抽象方法, 而且继承自 Iterable
类;__iter__
抽象方法则在 Iterable
类中定义,如图所示:
Iterator
抽象基类实现 __iter__
方法的方式是返回实例本身(return self
)。这样,在需 要可迭代对象的地方可以使用迭代器。如下示例 是 abc.Iterator
类的源码:
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
if (any("__next__" in B.__dict__ for B in C.__mro__) and
any("__iter__" in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented
在 Python 3 中,Iterator
抽象基类定义的抽象方法是 it.__next__()
,而在 Python 2 中是 it.next()
。一如既往,我们应该避免直接调用特殊方法,使用 next(it)
即可,这个内置的函数在 Python 2 和 Python 3 中都能使用。
注意, abc.Iterator
抽象基类中 __subclasshook__
方法的作用,使得白鹅类型可以检测的原因。,检查对象 x
是否为迭代器最好的方式是调用 isinstance(x, abc.Iterator)
。得 益于Iterator.__subclasshook__
方法,即使对象x
所属的类不是Iterator
类的真实子类或虚拟子类,也能这样检查。
下面在来看一下 Sentence
类:构建iter()
迭代器和使用next()
import re
import reprlib
from collections import abc
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __getitem__(self, index):
return self.words[index]
def __len__(self):
return len(self.words)
def __repr__(self):
# reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
return 'Sentence(%s)' % reprlib.repr(self.text)
if __name__ == '__main__':
# s = Sentence('"The time has come," the Walrus said,')
# print(s)
# for word in s:
# print(word)
#
# 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代的类型。
# print(list(s))
# print(iter(s))
# print(isinstance(s, abc.Iterable))
s3 = Sentence('Pig and Pepper')
it = iter(s3)
print(it)
print(next(it))
print(next(it))
print(next(it))
# print(next(it))
print(list(it))
print(list(iter(s3)))
运行结果:
因为迭代器只需 __next__
和 __iter__
两个方法,所以除了调用 next()
方法,以及捕获 StopIteration
异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原” 迭代器。如果想再次迭代,那就要调用 iter(...)
,传入之前构建迭代器的可迭代对象。 传入迭代器本身没用,因为前面说过 Iterator.__iter__
方法的实现方式是返回实例本身, 所以传入迭代器无法还原已经耗尽的迭代器。
总结:
迭代器:迭代器是这样的对象:实现了无参数的 __next__
方法,返回序列中的下一个元素;如 果没有元素了,那么抛出 StopIteration
异常。Python 中的迭代器还实现了 __iter__
方 法,因此迭代器也可以迭代。