流畅的Python 可迭代的对象、迭代器和生成器

单词序列Sentence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import re
import reprlib

RE_WORD = re.compile("\w+")


class Sentence:
'''
定义了一个 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):
return len(self.text)

def __repr__(self):
print(reprlib.repr(self.text))
return f"{type(self).__name__}({self.text}):{self.words}"


sentence = Sentence('"The time has come," Tom said,')
print(sentence)
for i in sentence:
print(i, end=' ')
print("\n" + sentence[0], sentence[-1])

序列可以迭代的原因: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__ 方法,因此也应该这么做。

检查一个对象是否可迭代:

1
2
3
4
5
6
from collections import abc

print(iter(sentence)) # <iterator object at 0x000001E1A53E7AC8>
print(isinstance(sentence, abc.Iterable)) # False
s = 1
print(iter(s)) # TypeError: 'int' object is not iterable

检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理TypeError 异常。 这比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x) 函数会考虑到遗留的 __getitem__ 方法,而 abc.Iterable 类则不考虑。


可迭代的对象与迭代器的对比

可迭代的对象:
使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__方法,那么对象就是可迭代的。序列都可以迭代;另外,实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种对象也可以迭代。

可迭代的对象迭代器之间的关系:Python 从可迭代的对象中获取迭代器

字符串 ‘abc’ 是可迭代的对象。背后是有迭代器的,只不过我们看不到:

1
2
3
s = "abc"
for i in s:
print(i)

如果没有 for 语句,不得不使用 while 循环模拟:

1
2
3
4
5
6
7
s_iter = iter(s)   # 使用可迭代的对象构建迭代器 s_iter。
while True:
try:
print(next(s_iter))
except StopIteration:
del s_iter # 释放对s_iter的引用,即废弃迭代器对象。
break

StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for 循环和其他迭代上下文(如列表推导、元组拆包,等等)中的 StopIteration 异常。

标准的迭代器接口有两个方法:

  • __next__
    返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。

  • __iter__
    返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

这个接口在 collections.abc.Iterator 抽象基类中制定。这个类定义了 __next__ 抽象方法,而且继承自 Iterable 类;__iter__ 抽象方法则在 Iterable 类中定义。

image-20250222001639860

图解:Iterable 和 Iterator 抽象基类。以斜体显示的是抽象方法。具体的 Iterable.__iter__ 方法应该返回一个 Iterator 实例。具体的 Iterator 类必须实现 __next__ 方法。Iterator.__iter__ 方法直接返回实例本身。

Iterator 抽象基类实现 __iter__ 方法的方式是返回实例本身(return self)。这样,在需要可迭代对象的地方可以使用迭代器。 abc.Iterator 类的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# from Lib._collections_abc import Iterator

class Iterable(metaclass=ABCMeta):

__slots__ = ()

@abstractmethod
def __iter__(self):
while False:
yield None

@classmethod
def __subclasshook__(cls, C):
if cls is Iterable:
return _check_methods(C, "__iter__")
return NotImplemented


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:
return _check_methods(C, '__iter__', '__next__')
return NotImplemented

检查对象 x 是否为迭代器最好的方式是调用 isinstance(x, abc.Iterator),相应的,检查是都是迭代对象用 isinstance(x, abc.Iterable)。

1
2
3
from collections import abc
s = "abc"
print(isinstance(iter(s), abc.Iterator)) # True

迭代器只需 __next____iter__ 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原”迭代器。如果想再次迭代,那就要调用 iter(…),传入之前构建迭代器的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法的实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s1 = Sentence("I am zyp")
iter_s1 = iter(s1) # 使用 iter(...)函数构建迭代器

print(isinstance(iter_s1, abc.Iterable)) # True
print(isinstance(iter_s1, abc.Iterator)) # True

print(next(iter_s1)) # I 使用 next(...) 函数使用迭代器
print(next(iter_s1)) # am
print(next(iter_s1)) # zyp
# print(next(iter_sentence)) # StopIteration
print(list(iter_s1)) # []

print(next(iter(s1))) # I # 重新传入可迭代对象
print(list(iter(s1))) # ['I', 'am', 'zyp']

迭代器
迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。