迭代是数据处理的基石。
内存中放不下的数据集,我们要找到一种惰性获取数据的方式,即一次获取一个数据项,这就是迭代器模式(iterator pattern)。
在Python2.2加入了yield关键字,这个关键字用于构建生成器(generator),其作用和迭代器一样。
所有的生成器都是迭代器。
因为生成器完全实现了迭代器接口。
迭代器用于从集合中取出元素。
生成器用于“凭空”生成元素。比如斐波那契数列中的数有无数个,在集合中放不过下。
在Python社区中,大多数时候都把迭代器和生成器视为同一概念。
迭代器用于支持:for循环、构架你和扩展集合类型、逐行遍历文本文件、列表推导;字典推导;集合推导、元组拆包、调用函数的*拆包实参。
实现可迭代对象:Sentence类
实现一个Sentence类,把句子拆分为单词序列。
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text) # 返回一个列表,元素是匹配项
def __getitem__(self, item):
return self.words[item]
def __len__(self): # 完善序列协议,实现了len方法。可迭代对象,没必要实现这个
return len(self.words)
def __repr__(self):
class_name = type(self).__name__
return "{}({})".format(class_name, reprlib.repr(self.text)) # 大型数据结构的省略字符串表示形式
s = Sentence('we love you')
print(s)
for word in s:
print(word)
打印
Sentence('we love you')
we
love
you
知识点:
reprlib.repr前面章节提到过,是以省略号...表示过多的元素。
序列可迭代的原因:iter函数
解释器在调用可迭代对象x时,会自动调用iter(x)
内置的iter函数有以下作用:
检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素。
如果以上都调用失败,Python会抛出TypeError异常,提示 X Object yes not Iterable
任何的Python序列可迭代的原因,它们都实现了__iter__方法或者至少实现了__getitem__方法。
这是鸭子类型的极端形式,只是实现了__iter__或者实现__getitem__方法且此方法的参数是从0开始的证书,就认为对象是可迭代的。
不需要创建子类,也不用注册虚拟子类,因为abc.Iterable类实现了__subclasshook__方法
示例,只要实现了__iter__方法,就是Iterable子类
from collections import abc
class Foo:
def __iter__(self):
pass
f = Foo()
print(isinstance(f, abc.Iterable))
print(issubclass(Foo, abc.Iterable))
打印
True
True
判断是否可迭代对象
要注意的是,虽然Sentence类是可迭代的,但是不过通过issubclass(Sentence, abc.Iterable)的测试,因为没有实现__iter__方法。
在Python3.4开始,检查一个对象是否可迭代,最准确的方法是调用iter(x)函数,如果不可迭代,再处理TypeError异常。
这比使用isinstance(x, abc.Iterable)更准确,因为iter(x)函数会考虑到遗留的__getitem__方法,而abc.Iterable类则不考虑。
因为只要实现了__getitem__方法就是可迭代的,不一定要实现__iter__。
可迭代的对象和迭代器
可迭代的对象定义:
使用iter函数可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法,那么对象就是可迭代的。
序列都可以迭代:实现了__getitem__方法,而且其参数是从零开始的索引,这种对象也可以迭代。
要明确可迭代对象和迭代器的关系:Python从可迭代对象中获取迭代器。
比如下面的for循环,迭代一个字符串。这里的"ABC"就是可迭代对象,背后有迭代器,只是我们看不到,for是迭代上下文,自动帮我们处理了:
>>> for char in "ABC":
... print(char)
...
A
B
C
如果不适用for语句,使用while模拟:
s = "ABC"
it = iter(s)
while True:
try:
print(next(it))
except StopIteration:
del it
break
知识点:
可迭代对象使用iter()函数变为迭代器。
使用next函数不断的在迭代器中获取下一个字符。
如果没有字符了,迭代器抛出StopIteration异常。表明迭代器已经到头了。Python会处理for循环、列表推导、元组拆包等待迭代器上下文中的StopIteration异常
标准的迭代器接口有两个方法。
__next__:返回下一个可用元素,如果没有元素了,抛出StopIteration异常
__iter__:返回self,以便在应该使用可迭代对象的地方使用迭代器,比如在for循环中
这个接口在collection.abc.Iterator抽象基类中制定。这个类定义了__next__抽象方法。这个类继承自Iterable类,__iter__抽象方法是在Iterable类中定义。
关系图:

示例,abc.Iterator类源码
from collections import Iterable
from abc import abstractmethod
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
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
在Python3中,Iterator抽象基类定义的抽象方法是it.__next__();在Python2中是it.next()。我们应该避免直接调用特殊方法,使用iter(it)即可,在Python2和3中都能使用。
因为迭代器只需要实现__next__和__iter__两个方法,所以除了调用next()函数,然后捕获StopIteration异常之外,没有别的办法能检查是否还有遗留的元素。
也没有办法能还原迭代器。如果想要再次迭代,只能再调用iter()传入之前的可迭代对象。传入迭代器本身是没用的,因为Iterator.__iter__方法的实现是返回实例本身。
迭代器定义:
迭代器是这样的对象:
1.实现了无参数的__next__方法,返回序列中的下一个元素;
2.如果没有元素了,会抛出StopIteration异常;
3.Python中的迭代器实现了__iter__方法,因此迭代器也可以迭代。
判断是否迭代器
检查对象x是否为迭代器的最好方式是调用isinsta