Python 迭代过程浅析

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

主要步骤:

  1. a 传递给内置函数 iter ,得到返回值 b
  2. 迭代开始。
  3. b 传递给内置函数 next ,得到返回值,赋值给循环变量 i
  4. 执行循环体语句,此处为 print(i)
  5. 重复步骤2 3,直到 next 函数抛出 StopIteration 异常。
  6. 迭代结束。

内置函数 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 迭代过程浅析_第1张图片
最后,需要补充的是,当 Python 无法 __iter____next__ 的机制来进行迭代的时候,会检查备选的 __getitem__ 方案,这不在本文的讨论范围内。

你可能感兴趣的:(Python学习)