猿问

python 3 lambda 的参数绑定似乎已损坏

我在 CentOS 7 环境中使用 Python 3.6.10。我正在尝试创建一个基于结构化规范执行的命令列表。将其视为 lambda 列表似乎很自然且符合 Python 风格。我通过遍历规范来构建 lambda 列表。令我惊讶的是,当我执行结果时,我发现每个 lambda 都是相同的,因为它在创建 lambda 时没有捕获其参数。我认为这是一个错误。


以下是说明该行为的示例代码:


specification = {

    'labelOne': ['labelOne.one', 'labelOne.two', 'labelOne.three', 'labelOne.four', 'labelOne.five'],

    'labelTwo': ['labelTwo.one', 'labelTwo.two', 'labelTwo.three', 'labelTwo.four', 'labelTwo.five'],

    'labelThree': ['labelThree.one', 'labelThree.two', 'labelThree.three', 'labelThree.four', 'labelThree.five'],

    'labelFour': ['labelFour.one', 'labelFour.two', 'labelFour.three', 'labelFour.four', 'labelFour.five'],

    'labelFive': ['labelFive.one', 'labelFive.two', 'labelFive.three', 'labelFive.four', 'labelFive.five'],

    }


lambdas = []

for label, labelStrings in specification.items():

    for labelString in labelStrings:

        lambdaString = f"""Label: \"{label}\" with labelString: \"{labelString}\""""

        oneArgLambda = lambda someArg: print(someArg, lambdaString)

        lambdas.append(oneArgLambda)


for each in lambdas:

    each('Show: ')

我期望看到这个:


Show:  Label: "labelOne" with labelString: "labelOne.one"

Show:  Label: "labelOne" with labelString: "labelOne.two"

Show:  Label: "labelOne" with labelString: "labelOne.three"

Show:  Label: "labelOne" with labelString: "labelOne.four"

Show:  Label: "labelOne" with labelString: "labelOne.five"

Show:  Label: "labelTwo" with labelString: "labelTwo.one"

Show:  Label: "labelTwo" with labelString: "labelTwo.two"

Show:  Label: "labelTwo" with labelString: "labelTwo.three"

Show:  Label: "labelTwo" with labelString: "labelTwo.four"


lambda 的参数绑定是在执行 lambda 时发生的,而不是在创建 lambda 时发生的。这至少是出乎意料的,而且我认为可以说是错误的。

我认为 lambda 尽管有局限性,但应该创建一个 CLOSURE —— 它的整个目的是捕获其参数在创建时的状态,以便稍后在计算 lambda 时可以使用它们。这就是为什么它被称为“闭包”,因为它在创建时关闭了其参数的值。

我有什么误解吗?


慕娘9325324
浏览 114回答 3
3回答

德玛西亚99

正如您所说,它创建了 CLOSURE,并且闭包在lambdaString的上部范围中使用指定变量,但技巧是所有 lambda 都使用对lambdaString的相同引用,并且因为每次它记住最后一个时都更改它。例如:c = ['one', 'two']res = []for i in range(2):    for y in c:        def la(x):            print(x, y)        res.append(la)for la in res:    la('Show: ')# Show:  two# Show:  two# Show:  two# Show:  two你只需要另一个关闭来防止这种情况c = ['one', 'two']res = []for i in range(2):    for y in c:        def closure_y(y):            def la(x):                print(x, y)            return la                    res.append(closure_y(y))for la in res:    la('Show: ')# Show:  one# Show:  two# Show:  one # Show:  two完整代码可能是specification = {    'labelOne': ['labelOne.one', 'labelOne.two', 'labelOne.three', 'labelOne.four', 'labelOne.five'],    'labelTwo': ['labelTwo.one', 'labelTwo.two', 'labelTwo.three', 'labelTwo.four', 'labelTwo.five'],    'labelThree': ['labelThree.one', 'labelThree.two', 'labelThree.three', 'labelThree.four', 'labelThree.five'],    'labelFour': ['labelFour.one', 'labelFour.two', 'labelFour.three', 'labelFour.four', 'labelFour.five'],    'labelFive': ['labelFive.one', 'labelFive.two', 'labelFive.three', 'labelFive.four', 'labelFive.five'],    }lambdas = []for label, labelStrings in specification.items():    for labelString in labelStrings:        lambdaString = f"""Label: \"{label}\" with labelString: \"{labelString}\""""        def clousure(lambdaString):            oneArgLambda = lambda someArg: print(someArg, lambdaString)            return oneArgLambda        lambdas.append(clousure(lambdaString))for each in lambdas:    each('Show: ')

婷婷同学_

您还有一些其他评论和答案来解释正在发生的事情,并且您的问题“有趣”是因为它迫使读者对代码和各种绑定问题感到困惑。但是,如果我在审查过程中从同事那里看到了您的代码,我会要求重写 - 不是因为我会立即知道存在错误,而是因为它需要太多的头脑来思考是否来自周围的绑定(并改变) 范围将完全按照希望的方式运行。相反,坚持在你的程序中实行更严格的纪律,从而减轻你的读者(大多数时候谁是你)的认知负担。具体来说,将函数创建移至真正隔离的范围,并将所有不同的输入传递给该函数创建者。这种方法是可靠的,因为它要么在第一次尝试时起作用,要么完全失败(如果您忽略将所有需要的参数传递给函数创建者)。一种方法是:# A function to create another function, with non-surprising argument binding.# We expect nothing from the surrounding scope. All business can be done locally.def get_func(label, x):    return lambda prefix: print(f'''{prefix} => {label}: {x}''')# Some input data.specification = {    label : [label + str(n) for n in range(3)]    for label in ('A', 'B', 'C')}# Use that data to create some functions.funcs = [    get_func(label, x)    for label, xs in specification.items()    for x in xs]# Run 'em.for f in funcs:    f('Show')

幕布斯7119047

这是一个替代方案lambdas = []for label, labelStrings in specification.items():    for labelString in labelStrings:        lambdaString = f"""Label: \"{label}\" with labelString: \"{labelString}\""""        oneArgLambda = lambda someArg, lambdaString: print(someArg, lambdaString)        lambdas.append((oneArgLambda, lambdaString))for f, lambdaString in lambdas:    f('Show: ', lambdaString)
随时随地看视频慕课网APP

相关分类

Python
我要回答