这种行为实际上与参数的传递方式几乎没有关系(这总是相同的方式;在 Python 中没有区别,有时通过引用传递,有时通过值传递)。相反,问题在于如何找到名称本身。
lambda: i
创建一个当然等效于的函数:
def anonymous():
return i
那i
是一个名称,在 的范围内anonymous
。但它永远不会在该范围内绑定(甚至不是作为参数)。因此,这意味着任何东西都i
必须是某个外部范围的名称。为了找到合适的名称i
,Python 将查看在anonymous
源代码中定义的范围(然后从那里类似地),直到找到i
. 1
所以这个循环:
foo = []
for i in range(3):
foo.append((lambda: i))
for l in foo:
print(l())
几乎就像你写的一样:
foo = []
for i in range(3):
def anonymous():
return i
foo.append(anonymous)
for l in foo:
print(l())
因此i
in return i
(or lambda: i
) 最终i
与外部范围相同,即循环变量。并不是说它们都是对同一个对象的引用,而是它们都是同名的。因此,存储在其中的函数根本不可能foo
返回不同的值;它们都返回由一个名称引用的对象。
为了证明这一点,请观察当我在循环后删除变量时会发生什么:i
>>> foo = []
>>> for i in range(3):
foo.append((lambda: i))
>>> del i
>>> for l in foo:
print(l())
Traceback (most recent call last):
File "<pyshell#7>", line 2, in <module>
print(l())
File "<pyshell#3>", line 2, in <lambda>
foo.append((lambda: i))
NameError: global name 'i' is not defined
您可以看到问题不在于每个函数都有一个本地i
绑定到错误的东西,而是每个函数都返回相同全局变量的值,我现在已经删除了它。
OTOH,当您的循环如下所示时:
foo = []
for i in range(3):
foo.append((lambda i=i: i))
for l in foo:
print(l())
就是这样:
foo = []
for i in range(3):
def anonymous(i=i):
return i
foo.append(anonymous)
for l in foo:
print(l())
现在i
inreturn i
与外部范围不同;i
它是函数的局部变量anonymous
。在循环的每次迭代中都会创建一个新函数(临时存储在外部范围变量anonymous
中,然后永久存储在 的插槽中foo
),因此每个函数都有自己的局部变量。
在创建每个函数时,其参数的默认值设置为i
(在定义函数的范围内)的值。与变量的任何其他“读取”一样,它会提取当时变量引用的任何对象,此后与变量没有任何联系。2
因此,每个函数在i
创建时都在外部作用域中获取默认值,然后在没有参数的情况下调用该函数时,该默认值将成为该i
函数本地作用域中的值。每个函数都没有非本地引用,因此完全不受其外部发生的事情的影响。
1这是在“编译时”(将 Python 文件转换为字节码时)完成的,不考虑系统在运行时的样子;它几乎是在寻找源代码中的外部def
块。i = ...
所以局部变量实际上是静态解析的!如果该查找链一直落到模块全局范围内,那么 Python 假定i
它将在代码运行时在全局范围内定义,并且i
无论是否存在静态变量,都将其视为全局变量在模块范围内可见绑定i
,因此您可以动态创建全局变量而不是本地变量。
2令人困惑的是,这意味着在 中lambda i=i: i
,三个i
s 指的是一行中两个不同范围内的三个完全不同的“变量”。
最左边i
的是“名称”,其中包含将用于默认值的值i
,该值独立于函数的任何特定调用而存在;它几乎完全是存储在函数对象中的“成员数据”。
第二个i
是在创建函数时评估的表达式,以获取默认值。所以该i=i
位的行为非常像一个独立的语句,在包含表达式the_function.default_i = i
的同一范围内进行评估。lambda
最后第三个i
实际上是函数内部的局部变量,它只存在于对匿名函数的调用中。