为什么是[6,6,6,6]
def num():
return [lambda x: i * x for i in range(4)]
result = [m(2) for m in num()]
print(result)
wait …
先不急往下看,请在脑海里先推测一下这段代码的运行结果,!!看看是否符合自己的常规理解
go on ↓
这段代码的输出结果是[6, 6, 6, 6],而不是预期的[0, 2, 4, 6]。这是因为在 Python 中,函数闭包的特性可能导致这种意外的行为。
什么是闭包
闭包是一种特殊的函数,它具有访问外部函数作用域中变量的能力。这意味着它可以“捕获”外部函数中的变量,并在其生命周期内保持对它们的引用。
让我们看看一个简单的例子:
def outer():
x = 1
def inner():
print(x)
return inner
my_func = outer()
my_func() # 1
在这个例子中,函数 inner()
是一个闭包,因为它可以访问外部函数 outer()
中的变量 x
。我们将 outer()
函数的返回值赋给变量 my_func
,然后调用 my_func()
。此时,my_func()
调用内部函数 inner()
,它可以访问外部变量 x
的值,并将其打印出来。
代码分析
现在,让我们回到原始代码并解释为什么它会输出[6, 6, 6, 6, 6]。
def num():
return [lambda x: i * x for i in range(4)]
result = [m(2) for m in num()]
print(result)
# [6, 6, 6, 6]
在这段代码中,函数 num()
返回一个列表,其中包含 4 个 lambda 函数。这些 lambda 函数使用变量 i
和参数 x
来计算一个值。在每次迭代中,变量 i
的值都会增加 1,最终达到 3。
然后,代码创建一个新列表 result
,其中包含调用这些 lambda 函数的结果。在这里,每个 lambda 函数都调用 i * x
,其中 i
是 0 到 3 之间的值。然而,由于 lambda 函数是闭包,它们将引用外部作用域中的变量 i
。因此,当 m(2)
被调用时,每个 lambda 函数都将引用最终的值 3。
因此,对于每个 lambda 函数,调用 m(2)
将返回 3 * 2
的值 6。这就是为什么结果列表包含四个 6 的原因。
如何避免这个问题
如果嵌套函数引用了上层作用域中的一个变量,改变量被循环改变,循环结束后,该变量的值,为最后一次循环完成时被引用变量的值。
如果想要让这类代码能够工作,那么需要使用默认参数把当前的值传递给嵌套作用域的变量。而不是在 lambda 函数内部引用它们。
例如:
def num():
return [lambda x, i=a: i * x for a in range(4)]
result = [m(2) for m in num()]
print(result)
# [0, 2, 4, 6]