Why aren't python nested functions called closures?
Почему вложенные функции python не называются замыканиями?
Я видел и использовал вложенные функции в Python, и они соответствуют определению замыкания. Так почему же они называются "вложенные функции" вместо "замыканий"?
Являются ли вложенные функции не замыканиями, потому что они не используются внешним миром?
ОБНОВЛЕНИЕ: Я читал о замыканиях, и это заставило меня задуматься об этой концепции применительно к Python. Я искал и нашел статью, упомянутую кем-то в комментарии ниже, но я не смог полностью понять объяснение в этой статье, вот почему я задаю этот вопрос.
Переведено автоматически
Ответ 1
Закрытие происходит, когда функция имеет доступ к локальной переменной из окружающей области, которая завершила свое выполнение.
При вызове make_printer в стек помещается новый фрейм с скомпилированным кодом для printer функции в качестве константы и значением msg в качестве локального. Затем он создает и возвращает функцию. Поскольку функция printer ссылается на msg переменную, она остается активной после того, как make_printer функция вернулась.
Итак, если ваши вложенные функции не
доступ к переменным, которые являются локальными для охватывающих областей,
делайте это, когда они выполняются за пределами этой области,
тогда они не являются замыканиями.
Вот пример вложенной функции, которая не является замыканием.
Здесь мы привязываем значение к значению параметра по умолчанию. Это происходит, когда функция printer создается, и поэтому после msg возврата не требуется поддерживать ссылку на значение printer external to make_printer. msg в данном контексте это просто обычная локальная переменная функции printer.
Однако кому-то может быть интересно, как переменные хранятся под капотом.
Прежде чем перейти к фрагменту:
Замыкания - это функции, которые наследуют переменные из окружающей их среды. Когда вы передаете обратный вызов функции в качестве аргумента другой функции, которая будет выполнять ввод-вывод, эта функция обратного вызова будет вызвана позже, и эта функция — почти волшебным образом — запомнит контекст, в котором она была объявлена, вместе со всеми переменными, доступными в этом контексте.
Если функция не использует свободные переменные, она не формирует замыкание.
Если есть другой внутренний уровень, который использует свободные переменные - все предыдущие уровни сохраняют лексическую среду ( пример в конце )
атрибуты функций func_closure в python < 3.X или __closure__ в python > 3.X сохраняют свободные переменные.
Каждая функция в python имеет атрибут closure , но если свободных переменных нет, она пуста.
пример: с атрибутами закрытия, но без содержимого внутри, поскольку нет свободной переменной.
И все функции Python имеют атрибут closure, поэтому давайте рассмотрим заключающие переменные, связанные с функцией closure.
Вот атрибут func_closure для функции printer
>>> 'func_closure'indir(printer) True >>> printer.func_closure (<cell at 0x108154c90: strobject at 0x108151de0>,) >>>
Атрибут closure возвращает набор объектов cell, которые содержат сведения о переменных, определенных во внешней области видимости.
Первый элемент в func_closure, который может быть None или набором ячеек, содержащих привязки для свободных переменных функции, доступен только для чтения.
In the above snippet, I didn't print msg inside the printer function, so it doesn't create any free variable. As there is no free variable, there will be no content inside the closure. Thats exactly what we see above.
Now I will explain another different snippet to clear out everything Free Variable with Closure:
>>> defouter(x): ... defintermediate(y): ... free = 'free' ... definner(z): ... return'%s %s %s %s' % (x, y, free, z) ... return inner ... return intermediate ... >>> outer('I')('am')('variable') 'I am free variable' >>> >>> inter = outer('I') >>> inter.func_closure (<cell at 0x10c989130: strobject at 0x10c831b98>,) >>> inter.func_closure[0].cell_contents 'I' >>> inn = inter('am')
So, we see that a func_closure property is a tuple of closure cells, we can refer them and their contents explicitly -- a cell has property "cell_contents"
>>> inn.func_closure (<cell at 0x10c9807c0: strobject at 0x10c9b0990>, <cell at 0x10c980f68: strobject at 0x10c9eaf30>, <cell at 0x10c989130: strobject at 0x10c831b98>) >>> for i in inn.func_closure: ... print i.cell_contents ... free am I >>>
Here when we called inn, it will refer all the save free variables so we get I am free variable
>>> inn('variable') 'I am free variable' >>>
Ответ 3
Python имеет слабую поддержку замыкания. Чтобы понять, что я имею в виду, рассмотрим следующий пример счетчика, использующего замыкание с помощью JavaScript:
function initCounter(){ var x = 0; function counter () { x += 1; console.log(x); }; return counter; }
Замыкание довольно элегантно, поскольку оно дает функциям, написанным подобным образом, возможность иметь "внутреннюю память". Начиная с Python 2.7, это невозможно. Если вы попытаетесь
definitCounter(): x = 0; defcounter (): x += 1##Error, x not defined print x return counter
count = initCounter();
count(); ##Error count(); count();
Вы получите сообщение об ошибке, в котором говорится, что x не определен. Но как это может быть, если другие показали, что вы можете его распечатать? Это из-за того, как Python управляет переменной scope функций. Хотя внутренняя функция может читать переменные внешней функции, она не может их записывать.
Это действительно позор. Но с закрытием только для чтения вы можете, по крайней мере, реализовать шаблон декоратора функций, для которого Python предлагает синтаксический сахар.
Обновить
Как уже указывалось, есть способы справиться с ограничениями области видимости python, и я приведу некоторые из них.
1. Используйте global ключевое слово (в общем случае не рекомендуется).
2. В Python 3.x используйте nonlocal ключевое слово (предложено @unutbu и @leewz)
3. Определите простой модифицируемый класс Object
classObject(object): pass
и создайте Object scope внутри initCounter для хранения переменных
Поскольку scope на самом деле это просто ссылка, действия, выполняемые с ее полями, на самом деле не изменяют scope себя, поэтому ошибка не возникает.
4. Альтернативным способом, как указал @unutbu, было бы определить каждую переменную как массив (x = [0]) и изменить ее первый элемент (x[0] += 1). Опять же, ошибки не возникает, потому что x сам по себе не модифицируется.
5. Как предложил @raxacoricofallapatorius , вы могли бы создать x свойство counter
definitCounter ():
defcounter(): counter.x += 1 print counter.x
counter.x = 0 return counter
Ответ 4
В Python 2 не было замыканий - в нем были обходные пути, которые напоминали замыкания.
В уже приведенных ответах есть множество примеров - копирование переменных во внутреннюю функцию, изменение объекта во внутренней функции и т.д.
start = closure() another = closure() # another instance, with a different stack
start() # prints 1 start() # prints 2
another() # print 1
start() # prints 3
Ключевое слово nonlocal связывает внутреннюю функцию с явно упомянутой внешней переменной, фактически заключая ее в оболочку. Следовательно, более явно "замыкание".