Закрытие Python не работает должным образом

Когда я запускаю следующий script, оба lambda запускают 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()

Ответ 1

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

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

Ответ 2

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

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

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

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

lambda f=f: os.startfile(f)

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