Ответ 1
Ключи закрытия относятся к значениям, необходимым для функции, но они берутся из окружающей области.
Когда Python компилирует вложенную функцию, он замечает любые переменные, которые он ссылается, но определяется только родительской функцией (а не глобальными) в объектах кода как для вложенной функции, так и для родительской области. Это атрибуты co_freevars
и co_cellvars
для объектов __code__
этих функций соответственно.
Затем, когда вы фактически создаете вложенную функцию (которая происходит при выполнении родительской функции), эти ссылки затем используются для присоединения закрытия к вложенной функции.
Закрытие функции содержит набор ячеек, по одному для каждой свободной переменной (названный в co_freevars
); ячейки являются специальными ссылками на локальные переменные родительской области, которые соответствуют значениям, на которые указывают эти локальные переменные. Это лучше всего иллюстрируется примером:
def foo():
def bar():
print(spam)
spam = 'ham'
bar()
spam = 'eggs'
bar()
return bar
b = foo()
b()
В приведенном выше примере функция bar
имеет одну замыкающую ячейку, которая указывает на spam
в функции foo
. Ячейка следует за значением spam
. Что еще более важно, как только foo()
завершается и возвращается bar
, ячейка продолжает ссылаться на значение (строка eggs
), даже если переменная spam
внутри foo
больше не существует.
Таким образом, приведенный выше код выводит:
>>> b=foo()
ham
eggs
>>> b()
eggs
и b.__closure__[0].cell_contents
- 'eggs'
.
Обратите внимание, что замыкание разыменовывается при вызове bar()
; закрытие не фиксирует здесь значение. Это имеет значение, когда вы создаете вложенные функции (с выражениями lambda
или def
), которые ссылаются на переменную цикла:
def foo():
bar = []
for spam in ('ham', 'eggs', 'salad'):
bar.append(lambda: spam)
return bar
for bar in foo():
print bar()
Вышеуказанное будет печатать salad
три раза подряд, потому что все три функции lambda
ссылаются на переменную spam
, а не на значение, с которым она была связана, когда был создан объект функции. По завершении цикла for
spam
привязан к 'salad'
, поэтому все три закрытия будут разрешены к этому значению.