Python2与Python3中列表推导式调用函数的执行差异原因探究
Python 2 与 Python 3 执行差异原因分析
测试代码
from random import randint functions = [] for n in range(8): def f(): return f.t f.t = randint(1, 8) functions.append(f) # 1 result = [] for f in functions: result.append(f()) print(result) # 2 result = [] for f in functions: result.append(f.t) print(result) # 3 result = [f.t for f in functions] print(result) # 4 result = [f() for f in functions] print(result)
现象回顾
- 在 Python 2 中,4段代码输出完全一致。
- 在 Python 3 中,代码段4输出8个相同值(为其他代码段输出列表的最后一个元素),与另外三段存在差异。
差异原因
核心在于Python 2和3对列表推导式的作用域处理不同,结合函数的延迟绑定特性导致了这个结果:
- 函数的延迟绑定:循环中定义的
f()函数,内部引用的f是自由变量,不会在定义时绑定到当前函数对象,而是在调用时才去外部作用域查找f对应的变量。这里的f指向全局作用域中的变量。 - Python 2 列表推导式的作用域:Python 2的列表推导式没有独立作用域,直接使用全局作用域。代码段4的循环中,每次迭代会把当前函数对象赋值给全局变量
f,调用f()时,函数内部查找的全局f就是当前迭代的对象,因此能正确返回对应的f.t。 - Python 3 列表推导式的作用域:Python 3的列表推导式拥有独立局部作用域。代码段4中的循环变量
f是推导式内部的局部变量,不会修改全局的f。而全局的f在原循环结束后,指向最后一个创建的函数对象。调用f()时,函数内部查找的是全局f,因此所有调用都返回最后一个函数的f.t值。 - 其他代码段正常的原因:
- 代码段1的普通for循环在两个版本中都使用外部作用域,每次迭代更新全局
f,调用f()时能获取当前函数对象。 - 代码段2和3直接访问函数对象的
t属性,跳过了函数调用时的变量查找,无论作用域如何,都能直接获取当前迭代对象的属性值。
- 代码段1的普通for循环在两个版本中都使用外部作用域,每次迭代更新全局
内容的提问来源于stack exchange,提问作者Penghao.Yang
相关产品推荐
相关产品推荐

