python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python延迟绑定

一文带你了解Python中的延迟绑定

作者:答案永恒

Python中的延迟绑定是指在嵌套函数中,内部函数在被调用时才会绑定外部函数的变量,而不是在定义内部函数时就绑定。本文将通过一些例子带大家深入了解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())

输出结果为:

5
5
5
5
5

这是因为每个内部函数都引用了外部函数的 number 变量,但是这个变量在内部函数被调用时才会被绑定。由于 number 在每个迭代中的值都被重新赋值,所有内部函数都返回最后一个值,即 5。

为了避免延迟绑定可能导致的问题,可以通过将变量的值作为参数传递给内部函数来显式地绑定变量。例如,上面的代码可以修改如下:

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())

输出结果为:

1
2
3
4
5

在这个版本中,每个内部函数都有一个默认参数 number,它的默认值是外部循环的 number 变量。由于默认参数的值在内部函数被定义时就被确定了,所以每个内部函数都绑定了不同的变量值。

另一个典型的栗子

def multipliers(): 
    return [lambda x : i*x for i in range(4)] 
print([m(2) for m in multipliers()])

输出结果为:

[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 函数:

lambda x : i*x

转换为等价的普通函数:

def multiplier(x):
    return i*x

然后,我们将 multipliers() 函数中的 lambda 函数列表转换为等价的普通函数列表:

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 值。下面是一个修改后的代码:

def multipliers():
    return [lambda x, i=i : i*x for i in range(4)]
print([m(2) for m in multipliers()])

输出结果为:

[0, 2, 4, 6]

现在,每个 lambda 函数都有一个独立的 i 值,因此输出结果正确。

将 lambda 函数转换为等价的普通函数,可以更清晰地看到问题出在哪里。

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 值。

到此这篇关于一文带你了解Python中的延迟绑定的文章就介绍到这了,更多相关Python延迟绑定内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文