Закрытие 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
- не внешняя переменная, а параметр функции, который будет иметь значение, которое вы хотите по умолчанию (так что эта лямбда - это просто функция со значениями по умолчанию для параметры, не закрывая никакой лексической переменной).