Python 迭代过程浅析
mywang88
2019-07-17
简介
本文尝试浅析 Python 的迭代上下文的内部过程。
Python 版本为 3.7 。
示例
迭代上下文
Python 中最常见的迭代上下文是 for 循环语句:
for i in a:
print(i)
如果这段代码能够正常执行,我们就说对象 a
是可迭代的,即 Iterable
。
更为严格的说法是,((变量 a
所引用的对象)所属于的类)的实例对象是可迭代的,括号用来帮助断句。
简化的内部过程
为了更好地理解迭代过程,参考下列代码:
b = iter(a)
while True:
try:
i = next(b)
print(i)
except StopIteration:
break
主要步骤:
- 将
a
传递给内置函数iter
,得到返回值b
。 - 迭代开始。
- 将
b
传递给内置函数next
,得到返回值,赋值给循环变量i
。 - 执行循环体语句,此处为
print(i)
。 - 重复步骤2 3,直到
next
函数抛出StopIteration
异常。 - 迭代结束。
内置函数 iter
会调用 a
的 __iter__
方法,内置函数 next
会调用 b
的 __next__
方法。
不难发现,对象 a
的 __iter__
方法,以及对象 b
的 __next__
方法是一个对象可以被用来迭代的关键,而迭代过程的具体逻辑也由这两个方法定义。
需要补充的是,在 2.2 之后的 Python 版本中,应用了“新式类”的概念。在内置运算的上下文中,对一个对象的内置属性的引用,会跳过该对象本身的命名空间,直接在对象所属于的类(继承树)中进行查找。
构造可迭代对象
依据上一节的描述,我们可以自己构造一个可迭代对象:
class B:
def __next__(self):
return 'Still me'
class A:
def __iter__(self):
return B()
a = A()
for i in a:
print(i)
运行代码将不断打印 Still me
。
更简化的形式:
class B:
__next__ = lambda self: 'Still me'
class A:
__iter__ = B
我们只需要满足 __iter__
与 __next__
引用的对象是可调用的即可。
如果一个对象的 __iter__
方法返回其本身,且定义了 __next__
方法,那么它是一个“迭代器”对象。篇幅所限,本文只给出一个极简的示例:
class A:
__iter__ = lambda self: self
__next__ = lambda self: 'Still me'
本文中频繁使用 lambda
语句,是为了缩短篇幅,同时强调“可调用”这个概念。
更严格的内部过程
作为补充,给出以下示例:
b = a.__class__.__iter__(a)
while True:
try:
i = b.__class__.__next__(b)
print(i)
except StopIteration:
break
当对象 a
被用来迭代时,Python 内部并不会以 a.__iter__
的形式来引用对象 __iter__
属性,而是调用内置函数 iter(a)
。
这样一来,无论对象 a
本身是否有定义 __iter__
属性,Python 都会直接跳过它,去引用它所属于的类的 __iter__
属性。如果 a
所属于的类没有定义 __iter__
属性,Python 继续向上搜索类继承树,直到成功或失败。实际上,这正式 Python 的“新式类”的引用策略。
同理,对于 __next__
属性,Python 执行相同的处理过程。
为了更好帮助理解,笔者画了一张示意图:
最后,需要补充的是,当 Python 无法 __iter__
与 __next__
的机制来进行迭代的时候,会检查备选的 __getitem__
方案,这不在本文的讨论范围内。