Почему вложенные функции python не называются замыканиями?
Я видел и использовал вложенные функции в Python, и они соответствуют определению замыкания. Итак, почему они называются nested functions
вместо closures
?
Вложенные функции не замыкаются, потому что они не используются внешним миром?
ОБНОВЛЕНИЕ: Я читал о закрытии, и мне стало интересно об этом понятии относительно 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
, и поэтому после make_printer
не следует ссылаться на значение msg
external по отношению к printer
. msg
- это просто нормальная локальная переменная функции printer
в этом контексте.
Ответ 2
Вопрос уже ответил aaronasterling
Однако кто-то может быть заинтересован в том, как переменные хранятся под капотом.
Перед тем, как перейти к фрагменту:
Закрытие - это функции, которые наследуют переменные из окружающей среды. Когда вы передаете функцию обратного вызова в качестве аргумента другой функции, которая будет выполнять операции ввода-вывода, эта функция обратного вызова будет вызвана позже, и эта функция - почти волшебным образом - запомнит контекст, в котором он был объявлен, а также все доступные переменные в этом контексте.
-
Если функция не использует свободные переменные, она не образует замыкания.
-
Если есть другой внутренний уровень, который использует свободные переменные - все предыдущие уровни сохраняют лексическую среду (пример в конце)
-
атрибуты функции func_closure
в python < 3.X или __closure__
в python > 3.X сохраните свободные переменные.
-
Каждая функция в python имеет такие атрибуты закрытия, но не сохраняет содержимое, если нет свободных переменных.
пример: атрибутов закрытия, но без содержимого внутри, поскольку нет свободной переменной.
>>> def foo():
... def fii():
... pass
... return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>
NB: БЕСПЛАТНАЯ ПЕРЕМЕННАЯ ДОЛЖНА СОЗДАТЬ ЗАКРЫТИЕ.
Я объясню, используя тот же фрагмент, что и выше:
>>> def make_printer(msg):
... def printer():
... print msg
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer() #Output: Foo!
И все функции Python имеют атрибут закрытия, поэтому рассмотрим прилагаемые переменные, связанные с функцией закрытия.
Вот атрибут func_closure
для функции printer
>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
Атрибут closure
возвращает кортеж объектов ячейки, который содержит сведения о переменных, определенных в охватывающей области.
Первый элемент 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'>
>>>
Итак, когда мы вызываем функцию printer()
, она обращается к значению, хранящемуся внутри cell_contents
. Так мы получили результат как "Foo!"
Опять я объясню, используя приведенный выше фрагмент с некоторыми изменениями:
>>> def make_printer(msg):
... def printer():
... pass
... return printer
...
>>> printer = make_printer('Foo!')
>>> printer.func_closure
>>>
В приведенном выше фрагменте я не печатаю сообщение в функции принтера, поэтому он не создает никакой свободной переменной. Поскольку нет свободной переменной, в закрытии не будет содержимого. То, что мы видим выше.
Теперь я расскажу еще об одном другом фрагменте, чтобы очистить все Free Variable
с помощью 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')
Итак, мы видим, что свойство func_closure
является кортежем замыкающих ячеек, мы можем явно ссылаться на них и их содержимое - ячейка имеет свойство "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
>>>
Здесь, когда мы вызываем inn
, он будет ссылаться на все свободные переменные, поэтому мы получаем 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 управляет областью видимости переменных функций. Хотя внутренняя функция может читать переменные внешней функции, она не может их записывать.
Это действительно позор. Но только с закрытием только для чтения вы можете, по крайней мере, реализовать шаблон функции-декоратора, для которого 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()
start() # prints 1
start() # prints 2
start() # prints 3
Ключевое слово nonlocal
привязывает внутреннюю функцию к внешней переменной, явно упомянутой, фактически закрывая ее. Отсюда более явно "замыкание".
Ответ 5
У меня была ситуация, когда мне понадобилось отдельное, но постоянное пространство имен.
Я использовал классы. Я иначе.
Сегрегированные, но постоянные имена - это замыкания.
>>> class f2:
... def __init__(self):
... self.a = 0
... def __call__(self, arg):
... self.a += arg
... return(self.a)
...
>>> f=f2()
>>> f(2)
2
>>> f(2)
4
>>> f(4)
8
>>> f(8)
16
# **OR**
>>> f=f2() # **re-initialize**
>>> f(f(f(f(2)))) # **nested**
16
# handy in list comprehensions to accumulate values
>>> [f(i) for f in [f2()] for i in [2,2,4,8]][-1]
16
Ответ 6
def nested1(num1):
print "nested1 has",num1
def nested2(num2):
print "nested2 has",num2,"and it can reach to",num1
return num1+num2 #num1 referenced for reading here
return nested2
дает:
In [17]: my_func=nested1(8)
nested1 has 8
In [21]: my_func(5)
nested2 has 5 and it can reach to 8
Out[21]: 13
Это пример того, что такое закрытие и как его можно использовать.
Ответ 7
Я хотел бы предложить еще одно простое сравнение между примером python и JS, если это поможет сделать вещи более ясными.
JS:
function make () {
var cl = 1;
function gett () {
console.log(cl);
}
function sett (val) {
cl = val;
}
return [gett, sett]
}
и выполнение:
a = make(); g = a[0]; s = a[1];
s(2); g(); // 2
s(3); g(); // 3
Python:
def make ():
cl = 1
def gett ():
print(cl);
def sett (val):
cl = val
return gett, sett
и выполнение:
g, s = make()
g() #1
s(2); g() #1
s(3); g() #1
Причина:. Как и многие другие, упомянутые выше, в python, если во внутренней области есть присвоение переменной с тем же именем, создается новая ссылка во внутренней области. Не так с JS, если вы явно не объявляете одно с ключевым словом var
.