Почему вложенные функции python не называются замыканиями?
Я видел и использовал вложенные функции в Python, и они соответствуют определению замыкания. Так почему же они называются "вложенные функции" вместо "замыканий"?
Являются ли вложенные функции не замыканиями, потому что они не используются внешним миром?
ОБНОВЛЕНИЕ: Я читал о замыканиях, и это заставило меня задуматься об этой концепции применительно к Python. Я искал и нашел статью, упомянутую кем-то в комментарии ниже, но я не смог полностью понять объяснение в этой статье, вот почему я задаю этот вопрос.
Переведено автоматически
Ответ 1
Закрытие происходит, когда функция имеет доступ к локальной переменной из окружающей области, которая завершила свое выполнение.
def make_printer(msg):
def printer():
print(msg)
return printer
printer = make_printer('Foo!')
printer()
При вызове make_printer
в стек помещается новый фрейм с скомпилированным кодом для printer
функции в качестве константы и значением msg
в качестве локального. Затем он создает и возвращает функцию. Поскольку функция printer
ссылается на msg
переменную, она остается активной после того, как make_printer
функция вернулась.
Итак, если ваши вложенные функции не
- доступ к переменным, которые являются локальными для охватывающих областей,
- делайте это, когда они выполняются за пределами этой области,
тогда они не являются замыканиями.
Вот пример вложенной функции, которая не является замыканием.
def make_printer(msg):
def printer(msg=msg):
print(msg)
return printer
printer = make_printer("Foo!")
printer() #Output: Foo!
Здесь мы привязываем значение к значению параметра по умолчанию. Это происходит, когда функция printer
создается, и поэтому после msg
возврата не требуется поддерживать ссылку на значение printer
external to make_printer
. msg
в данном контексте это просто обычная локальная переменная функции printer
.
Ответ 2
На этот вопрос уже ответил аарон кастерлинг
Однако кому-то может быть интересно, как переменные хранятся под капотом.
Прежде чем перейти к фрагменту:
Замыкания - это функции, которые наследуют переменные из окружающей их среды. Когда вы передаете обратный вызов функции в качестве аргумента другой функции, которая будет выполнять ввод-вывод, эта функция обратного вызова будет вызвана позже, и эта функция — почти волшебным образом — запомнит контекст, в котором она была объявлена, вместе со всеми переменными, доступными в этом контексте.
Если функция не использует свободные переменные, она не формирует замыкание.
Если есть другой внутренний уровень, который использует свободные переменные - все предыдущие уровни сохраняют лексическую среду ( пример в конце )
атрибуты функций
func_closure
в python < 3.X или__closure__
в python > 3.X сохраняют свободные переменные.Каждая функция в python имеет атрибут closure , но если свободных переменных нет, она пуста.
пример: с атрибутами закрытия, но без содержимого внутри, поскольку нет свободной переменной.
>>> def foo():
... def fii():
... pass
... return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>
ПРИМЕЧАНИЕ: ДЛЯ СОЗДАНИЯ ЗАМЫКАНИЯ НЕОБХОДИМА СВОБОДНАЯ ПЕРЕМЕННАЯ.
Я объясню, используя тот же фрагмент, что и выше:
>>> def make_printer(msg):
... def printer():
... print msg
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer() #Output: Foo!
И все функции Python имеют атрибут closure, поэтому давайте рассмотрим заключающие переменные, связанные с функцией closure.
Вот атрибут func_closure
для функции printer
>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
Атрибут closure
возвращает набор объектов cell, которые содержат сведения о переменных, определенных во внешней области видимости.
Первый элемент в func_closure, который может быть None или набором ячеек, содержащих привязки для свободных переменных функции, доступен только для чтения.
>>> dir(printer.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__',
'__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>>
Здесь, в приведенном выше выводе, вы можете видеть cell_contents
, давайте посмотрим, что он хранит:
>>> printer.func_closure[0].cell_contents
'Foo!'
>>> type(printer.func_closure[0].cell_contents)
<type 'str'>
>>>
So, when we called the function printer()
, it accesses the value stored inside the cell_contents
. This is how we got the output as 'Foo!'
Again I will explain using the above snippet with some changes:
>>> def make_printer(msg):
... def printer():
... pass
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer.func_closure
>>>
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
:
>>> def outer(x):
... def intermediate(y):
... free = 'free'
... def inner(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: str object 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: str object at 0x10c9b0990>,
<cell at 0x10c980f68: str object at 0x10c9eaf30>,
<cell at 0x10c989130: str object 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;
}
count = initCounter();
count(); //Prints 1
count(); //Prints 2
count(); //Prints 3
Замыкание довольно элегантно, поскольку оно дает функциям, написанным подобным образом, возможность иметь "внутреннюю память". Начиная с Python 2.7, это невозможно. Если вы попытаетесь
def initCounter():
x = 0;
def counter ():
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
class Object(object):
pass
и создайте Object scope
внутри initCounter
для хранения переменных
def initCounter ():
scope = Object()
scope.x = 0
def counter():
scope.x += 1
print scope.x
return counter
Поскольку scope
на самом деле это просто ссылка, действия, выполняемые с ее полями, на самом деле не изменяют scope
себя, поэтому ошибка не возникает.
4. Альтернативным способом, как указал @unutbu, было бы определить каждую переменную как массив (x = [0]
) и изменить ее первый элемент (x[0] += 1
). Опять же, ошибки не возникает, потому что x
сам по себе не модифицируется.
5. Как предложил @raxacoricofallapatorius , вы могли бы создать x
свойство counter
def initCounter ():
def counter():
counter.x += 1
print counter.x
counter.x = 0
return counter
Ответ 4
В Python 2 не было замыканий - в нем были обходные пути, которые напоминали замыкания.
В уже приведенных ответах есть множество примеров - копирование переменных во внутреннюю функцию, изменение объекта во внутренней функции и т.д.
В Python 3 поддержка более явная и лаконичная:
def closure():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return inner
Использование:
start = closure()
another = closure() # another instance, with a different stack
start() # prints 1
start() # prints 2
another() # print 1
start() # prints 3
Ключевое слово nonlocal
связывает внутреннюю функцию с явно упомянутой внешней переменной, фактически заключая ее в оболочку. Следовательно, более явно "замыкание".