Python Lambdas和变量绑定

时间:2010-04-28 15:57:34

标签: python

我一直致力于自动构建的基本测试框架。下面的代码段代表了使用不同程序的两台机器之间通信的简单测试。在我实际进行任何测试之前,我想完全定义它们 - 所以下面的测试实际上并没有在声明所有测试之后运行。这段代码只是一个测试声明。

remoteTests = []
for client in clients:
    t = Test(
        name = 'Test ' + str(host) + ' => ' + str(client),
        cmds = [
            host.start(CMD1),
            client.start(CMD2),

            host.wait(5),

            host.stop(CMD1),
            client.stop(CMD2),
        ],
        passIf = lambda : client.returncode(CMD2) == 0
    )
remoteTests.append(t)

无论如何,在运行测试之后,它运行'passIf'定义的函数。由于我想为多个客户端运行此测试,我正在迭代它们并为每个客户端定义测试 - 没什么大不了的。但是,在第一个客户端上运行测试后,'passIf'会对客户列表中的最后一个进行评估,而不是lambda声明时的'client'。

我的问题是:python何时绑定lambdas中的变量引用?我想如果使用lambda外部的变量是不合法的,解释器就不知道我在说什么。相反,它默默地绑定到最后一个“客户端”的实例。

另外,有没有办法以我想要的方式强制解决?

2 个答案:

答案 0 :(得分:7)

client变量在外部作用域中定义,因此在lambda运行时,它将始终设置为列表中的最后一个客户端。

要获得预期结果,可以为lambda提供一个带有默认值的参数:

passIf = lambda client=client: client.returncode(CMD2) == 0

由于在定义lambda时评估默认值,因此其值将保持正确。

另一种方法是在函数内创建lambda:

def createLambda(client):
    return lambda: client.returncode(CMD2) == 0
#...
passIf = createLambda(client)

这里lambda引用client函数中的createLambda变量,它具有正确的值。

答案 1 :(得分:5)

您所遇到的passIf参数lambda是指封闭范围内的变量 client。它不是指变量client在创建时引用的对象,而是变量本身。如果在循环结束后调用这些passIf,则表示它们都引用循环中的最后一个值。 (在闭包术语中,Python的闭包是后期绑定,而不是早期绑定。)

幸运的是,将后期绑定闭包变为早期绑定闭包相当容易。你可以通过简单地给lambda一个参数作为默认值来绑定它来实现它:

passIf = lambda client=client: client.returncode(CMD2) == 0

这确实意味着该函数获得了额外的参数,如果偶然使用参数调用它,或者当您希望函数采用任意参数时,它可能会搞砸。所以另一种技术就是这样做:

# Before your loop:
def make_passIf(client):
    return lambda: client.returncode(CMD2) == 0

# In the loop
t = Test(
    ...
    passIf = make_passIf(client)
)