Вопрос-Ответ

Why aren't python nested functions called closures?

Почему вложенные функции 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 функция вернулась.

Итак, если ваши вложенные функции не


  1. доступ к переменным, которые являются локальными для охватывающих областей,

  2. делайте это, когда они выполняются за пределами этой области,

тогда они не являются замыканиями.

Вот пример вложенной функции, которая не является замыканием.

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

python