Я видел и использовал вложенные функции в Python, и они соответствуют определению замыкания. Так почему их называют nested functions
вместо closures
?
Разве вложенные функции не являются замыканиями, потому что они не используются внешним миром?
ОБНОВЛЕНИЕ: я читал о замыканиях, и это заставило меня задуматься об этой концепции относительно Python. Я искал и нашел статью, упомянутую кем-то в комментарии ниже, но я не мог полностью понять объяснение в этой статье, поэтому я задаю этот вопрос.
python
closures
nested-function
Срикар Аппалараджу
источник
источник
Ответы:
Закрытие происходит, когда функция имеет доступ к локальной переменной из охватывающей области, которая завершила свое выполнение.
Когда
make_printer
вызывается, новый стек помещается в стек с скомпилированным кодом дляprinter
функции в качестве константы и значениемmsg
как локальным. Затем он создает и возвращает функцию. Поскольку функцияprinter
ссылается наmsg
переменную, она сохраняется после ееmake_printer
возвращения.Итак, если ваши вложенные функции не
тогда они не замыкания.
Вот пример вложенной функции, которая не является замыканием.
Здесь мы привязываем значение к значению параметра по умолчанию. Это происходит при создании функции
printer
, поэтому после возврата не требуется сохранять ссылку на значениеmsg
внешнего значения . это просто нормальная локальная переменная функции в этом контексте.printer
make_printer
msg
printer
источник
self
. (В JavaScript / Python это почти так.)i
из окружающего объема» ». ссылается, т.е. может проверять (или изменять)i
значение, даже если / когда эта область «закончила свое выполнение», то есть выполнение программы перешло к другим частям кода. Блок, в которомi
определено, больше не существует, но функция (и), ссылающаяся на него,i
все еще может это сделать. Обычно это называется «закрытием по переменнойi
». Чтобы не иметь дело с конкретными переменными, это может быть реализовано как закрытие по всему фрейму среды, где определена эта переменная.Ааронастерлинг уже ответил на вопрос
Тем не менее, кто-то может быть заинтересован в том, как переменные хранятся под капотом.
Прежде чем перейти к фрагменту:
Замыкания - это функции, которые наследуют переменные из окружающей их среды. Когда вы передаете функцию обратного вызова в качестве аргумента другой функции, которая будет выполнять ввод / вывод, эта функция обратного вызова будет вызвана позже, и эта функция - почти волшебным образом - запомнит контекст, в котором она была объявлена, вместе со всеми доступными переменными в этом контексте.
Если функция не использует свободные переменные, она не образует замыкание.
Если есть другой внутренний уровень, который использует свободные переменные - все предыдущие уровни сохраняют лексическую среду (пример в конце)
Атрибуты функций
func_closure
в python <3.X или__closure__
в python> 3.X сохраняют свободные переменные.Каждая функция в python имеет атрибуты замыкания, но не сохраняет никакого содержимого, если нет свободных переменных.
пример: атрибутов замыкания, но внутри нет содержимого, так как нет свободной переменной.
NB: БЕСПЛАТНАЯ ПЕРЕМЕННАЯ ДОЛЖНА СОЗДАТЬ ЗАКРЫТИЕ.
Я объясню, используя тот же фрагмент, что и выше:
И все функции Python имеют атрибут замыкания, поэтому давайте рассмотрим переменные, связанные с функцией замыкания.
Вот атрибут
func_closure
для функцииprinter
closure
Атрибут возвращает кортеж объектов клеток , которые содержат информацию о переменных , определенных в рамках вмещающих.Первый элемент в func_closure, который может быть None или набором ячеек, которые содержат привязки для свободных переменных функции и доступны только для чтения.
Здесь в приведенном выше выводе вы можете увидеть
cell_contents
, давайте посмотрим, что он хранит:Итак, когда мы вызвали функцию
printer()
, она получает доступ к значению, хранящемуся внутриcell_contents
. Вот как мы получили вывод «Foo!»Я снова объясню использование приведенного выше фрагмента с некоторыми изменениями:
В приведенном выше фрагменте я не печатаю msg внутри функции принтера, поэтому она не создает свободной переменной. Поскольку свободной переменной нет, внутри замыкания не будет содержимого. Это именно то, что мы видим выше.
Теперь я объясню еще другой фрагмент кода , чтобы очистить все
Free Variable
сClosure
:Итак, мы видим, что
func_closure
свойство - это кортеж ячеек замыкания , мы можем явно ссылаться на них и их содержимое - ячейка имеет свойство «cell_contents»Здесь, когда мы вызывали
inn
, он будет ссылаться на все переменные сохранения, поэтому мы получаемI am free variable
источник
func_closure
теперь вызывается__closure__
, как и другиеfunc_*
атрибуты.__closure_
доступно в Python 2.6+ для совместимости с Python 3.__closure__
объект, который является замыканием.Python имеет слабую поддержку для закрытия. Чтобы понять, что я имею в виду, возьмем следующий пример счетчика, использующего замыкание с JavaScript:
Закрытие довольно элегантно, поскольку дает функциям, написанным таким образом, возможность иметь «внутреннюю память». Начиная с Python 2.7 это невозможно. Если вы попытаетесь
Вы получите сообщение о том, что x не определен. Но как это может быть, если другим было показано, что вы можете напечатать это? Это из-за того, как Python управляет областью видимости переменных функций. Хотя внутренняя функция может читать переменные внешней функции, она не может их записывать .
Это действительно позор. Но только с закрытием только для чтения вы можете по крайней мере реализовать шаблон декоратора функций, для которого Python предлагает синтаксический сахар.
Обновить
Как уже отмечалось, есть способы справиться с ограничениями области действия Python, и я расскажу о некоторых.
1. Используйте
global
ключевое слово (как правило, не рекомендуется).2. В Python 3.x используйте
nonlocal
ключевое слово (предложенное @unutbu и @leewz)3. Определите простой модифицируемый класс
Object
и создать
Object scope
внутриinitCounter
для хранения переменныхТак
scope
как на самом деле это просто ссылка, действия, предпринимаемые с его полями, на самом деле не изменяютscope
себя, поэтому ошибки не возникает.4. Альтернативный способ, как указал @unutbu, - определить каждую переменную как массив (
x = [0]
) и изменить ее первый элемент (x[0] += 1
). Снова ошибка не возникает, потому чтоx
сам не изменен.5. Как предлагает @raxacoricofallapatorius, вы можете сделать
x
свойствоcounter
источник
x = [0]
во внешней области и использоватьx[0] += 1
во внутренней области. В Python3 вы можете оставить свой код как есть и использовать нелокальное ключевое слово .x
указывает переменная, остается точно такой же, даже если вы вызываетеinc()
или что-то еще, и вы не выполняли эффективную запись в переменную.x
свойствоcounter
.nonlocal
ключевое слово, которое похожеglobal
на переменные внешней функции. Это позволит внутренней функции перепривязать имя из ее внешней функции. Я думаю, что «привязать к имени» является более точным, чем «изменить переменную».В Python 2 не было замыканий - были обходные пути, которые напоминали замыкания.
В ответах уже есть множество примеров - копирование переменных во внутреннюю функцию, изменение объекта во внутренней функции и т. Д.
В Python 3 поддержка более четкая и краткая:
Использование:
nonlocal
Ключевое слово связывает внутреннюю функцию к внешней переменной явно упомянутый, по сути , заключив его. Отсюда более явно «закрытие».источник
У меня была ситуация, когда мне нужно было отдельное, но постоянное пространство имен. Я использовал классы. Я не иначе. Сегрегированные, но постоянные имена являются замыканиями.
источник
дает:
Это пример того, что такое замыкание и как его можно использовать.
источник
Я хотел бы предложить еще одно простое сравнение между примером Python и JS, если это поможет прояснить ситуацию.
JS:
и выполнение:
Python:
и выполнение:
Причина: как уже говорили многие другие, в python, если во внутренней области видимости есть присвоение переменной с тем же именем, создается новая ссылка во внутренней области видимости. Не так с JS, если вы явно не объявите один с
var
ключевым словом.источник