Закрытие Python не работает, как ожидалось - PullRequest
11 голосов
/ 17 мая 2011

Когда я запускаю следующий скрипт, обе лямбды запускают os.startfile () в одном и том же файле - junk.txt. Я ожидаю, что каждая лямбда будет использовать значение "f", установленное при создании лямбды. Есть ли способ заставить это работать так, как я ожидаю?

import os


def main():
    files = [r'C:\_local\test.txt', r'C:\_local\junk.txt']
    funcs = []
    for f in files:
        funcs.append(lambda: os.startfile(f))
    print funcs
    funcs[0]()
    funcs[1]()


if __name__ == '__main__':
    main()

Ответы [ 2 ]

22 голосов
/ 17 мая 2011

Один из способов сделать это:

def main():
    files = [r'C:\_local\test.txt', r'C:\_local\junk.txt']
    funcs = []
    for f in files:
        # create a new lambda and store the current `f` as default to `path`
        funcs.append(lambda path=f: os.stat(path))
    print funcs

    # calling the lambda without a parameter uses the default value
    funcs[0]() 
    funcs[1]()

В противном случае f ищется при вызове функции, поэтому вы получаете текущее (после цикла) значение.

Способы, которые мне нравятся лучше:

def make_statfunc(f):
    return lambda: os.stat(f)

for f in files:
    # pass the current f to another function
    funcs.append(make_statfunc(f))

или даже (в Python 2.5+):

from functools import partial
for f in files:
    # create a partially applied function
    funcs.append(partial(os.stat, f))
4 голосов
/ 17 мая 2011

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

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

Из-за того, как определен язык, эти захваченные переменные «только для чтения» в Python 2.x: любое присваивание делает переменную локальной, если только она не объявлена ​​global (Python 3.x добавляет ключевое слово nonlocal в разрешить запись в локальную область видимости).

Как сказал Йохен Ритцель в своем ответе, общая идиома, позволяющая избежать захвата этой переменной и получить вместо нее захват значения, заключается в записи

lambda f=f: os.startfile(f)

это работает, потому что значения параметров по умолчанию оцениваются во время создания функции, а f это не внешняя переменная, а параметр функции, который будет иметь значение, которое вы хотите по умолчанию (так что эта лямбда-функция является просто функцией со значениями по умолчанию для параметры, больше не закрывая лексическую переменную).

...