延迟绑定是什么?
Python中的延迟绑定是指在嵌套函数中,内部函数在被调用时才会绑定外部函数的变量,而不是在定义内部函数时就绑定。这种绑定方式可以导致一些出乎意料的行为,因为变量的值是在函数调用时决定的,而不是在函数定义时。
具体来说,当一个嵌套函数引用了外部函数的变量时,Python会在内部函数被调用时搜索变量的值,而不是在内部函数定义时。这意味着如果外部函数的变量在内部函数被调用之前被改变了,内部函数将使用新的变量值,而不是定义时的值。这种行为可能会导致一些困惑和错误,特别是在使用嵌套函数进行编程时。
举个栗子
下面是一个例子,展示了延迟绑定的行为:
python
代码解读
复制代码
def outer():
numbers = [1, 2, 3, 4, 5]
funcs = []
for number in numbers:
def inner():
return number
funcs.append(inner)
return funcs
for func in outer():
print(func())
输出结果为:
shell
代码解读
复制代码
5
5
5
5
5
这是因为每个内部函数都引用了外部函数的 number
变量,但是这个变量在内部函数被调用时才会被绑定。由于 number
在每个迭代中的值都被重新赋值,所有内部函数都返回最后一个值,即 5。
为了避免延迟绑定可能导致的问题,可以通过将变量的值作为参数传递给内部函数来显式地绑定变量。例如,上面的代码可以修改如下:
python
代码解读
复制代码
def outer():
numbers = [1, 2, 3, 4, 5]
funcs = []
for number in numbers:
def inner(number=number):
return number
funcs.append(inner)
return funcs
for func in outer():
print(func())
输出结果为:
python
代码解读
复制代码
1
2
3
4
5
在这个版本中,每个内部函数都有一个默认参数 number
,它的默认值是外部循环的 number
变量。由于默认参数的值在内部函数被定义时就被确定了,所以每个内部函数都绑定了不同的变量值。
另一个典型的栗子
python
代码解读
复制代码
def multipliers():
return [lambda x : i*x for i in range(4)]
print([m(2) for m in multipliers()])
输出结果为:
shell
代码解读
复制代码
[6, 6, 6, 6]
是不是和你的想不一样呢!!为什么呢??
这是因为,在multipliers
函数中,返回的是一个包含四个 lambda 函数的列表,这些 lambda 函数的形式参数为 x
,函数体为 i*x
。当这些 lambda 函数被调用时,它们的 i
取决于它们在列表中的索引,而不是在定义时的值。因此,当我们在 [m(2) for m in multipliers()]
中迭代这些 lambda 函数并传递 2
作为参数时,所有 lambda 函数的 i
都是最后一个 i
的值,即 3
,因此所有的 lambda 函数都会返回 3*2=6
。
还不是很清楚?
没关系,让我们换一种方式解释下。
将 lambda 函数转换为等价的普通函数,可以更清晰地看到问题出在哪里。 首先,我们将原始的 lambda 函数:
python
代码解读
复制代码
lambda x : i*x
转换为等价的普通函数:
python
代码解读
复制代码
def multiplier(x):
return i*x
然后,我们将 multipliers()
函数中的 lambda 函数列表转换为等价的普通函数列表:
python
代码解读
复制代码
def multipliers():
funcs = []
for i in range(4):
def multiplier(x):
return i*x
funcs.append(multiplier)
return funcs
现在,我们可以更清晰地看到问题出在哪里。在原始的 lambda 函数中,i
是一个自由变量,它的值在函数调用时动态绑定。但是,在 multipliers()
函数中,每个 multiplier()
函数都使用了同一个自由变量 i
,其值在函数迭代结束后被设置为 3
。因此,当我们迭代这些函数并传递 2
作为参数时,每个函数都会乘以最后一个 i
的值,也就是 3
,所以结果会是 [6, 6, 6, 6]
。
如果要解决这个问题,可以使用闭包来捕获每个 lambda 函数所需的 i
值,使每个函数都有自己独立的 i
值。这样,当我们迭代这些函数并传递参数 2
时,每个函数都会乘以它们自己独立的 i
值,而不是最后一个 i
的值。
怎么避免这个问题呢
要避免这个问题,我们可以将 lambda 函数中的 i
变为默认参数,这样每个 lambda 函数都会有一个独立的 i
值。下面是一个修改后的代码:
python
代码解读
复制代码
def multipliers():
return [lambda x, i=i : i*x for i in range(4)]
print([m(2) for m in multipliers()])
输出结果为:
shell
代码解读
复制代码
[0, 2, 4, 6]
现在,每个 lambda 函数都有一个独立的 i
值,因此输出结果正确。
将 lambda 函数转换为等价的普通函数,可以更清晰地看到问题出在哪里。
python
代码解读
复制代码
def multipliers():
funcs = []
for i in range(4):
def multiplier(x, i=i):
return i*x
funcs.append(multiplier)
return funcs
这里我们使用了闭包来捕获每个 lambda 函数所需的 i
值,这样每个函数都有一个独立的 i
值。
现在,我们可以更清晰地看到问题出在哪里。在原始的 lambda 函数中,i
是一个自由变量,它的值在函数调用时动态绑定。但是,在 multipliers()
函数中,每个 multiplier()
函数都有自己独立的 i
值,这个值是在函数定义时静态绑定的。因此,当我们迭代这些函数并传递 2
作为参数时,所有函数的 i
值都是它们在定义时的值,而不是在调用时动态绑定的值。
通过使用闭包来捕获每个 lambda 函数所需的 i
值,我们可以解决这个问题,使每个函数都有自己独立的 i
值。