Python-лямбды и привязки переменных - PullRequest
6 голосов
/ 28 апреля 2010

Я работал над базовой структурой тестирования для автоматической сборки. Приведенный ниже фрагмент кода представляет собой простой тест связи между двумя машинами с использованием разных программ. Прежде чем я действительно сделаю какие-либо тесты, я хочу полностью определить их - так что этот тест ниже фактически не выполняется, пока все тесты не объявлены. Этот кусок кода является просто объявлением теста.

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» оценивает последний в списке клиентов, а не «клиент» во время лямбда-объявления.

Тогда мой вопрос: когда python связывает ссылки на переменные в лямбдах? Я подумал, что если использовать переменную извне лямбда, это не разрешено законом, интерпретатор понятия не имеет, о чем я говорю. Вместо этого он молча привязан к экземпляру последнего «клиента».

Кроме того, есть ли способ заставить разрешение так, как я его задумал?

Ответы [ 2 ]

7 голосов
/ 28 апреля 2010

Переменная client определяется во внешней области видимости, поэтому к моменту запуска lambda она всегда будет указывать на последнего клиента в списке.

Чтобы получить ожидаемый результат, вы можете задать лямбда-аргумент со значением по умолчанию:

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

Поскольку значение по умолчанию оценивается во время определения лямбды, его значение останется правильным.

Другой способ - создать лямбду внутри функции:

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

Здесь лямбда-выражение относится к переменной client в функции createLambda, которая имеет правильное значение.

5 голосов
/ 28 апреля 2010

В результате ваш passIf аргумент, лямбда, ссылается на переменную client из прилагаемой области видимости. Он ссылается не на объект, на который ссылается переменная client, а на саму переменную. Если вы вызываете их passIf после окончания цикла, это означает, что все они ссылаются на последнее значение в цикле. (В терминологии замыканий замыкания Python с поздним связыванием , а не с ранним связыванием .)

К счастью, довольно просто превратить замыкание с поздним связыванием в замыкание с ранним связыванием. Вы можете сделать это, просто задав лямбда-аргумент по умолчанию со значением, которое вы хотите связать:

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)
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...