Почему вложенные функции python не называются замыканиями?
Я видел и использовал вложенные функции в Python, и они соответствуют определению закрытия. Так почему же они называются nested functions
вместо closures
?
являются ли вложенные функции не закрытиями, потому что они не используются внешним миром?
обновление: Я читал о закрытии, и это заставило меня задуматься об этой концепции в отношении Python. Я искал и нашел статью, упомянутую кем-то в комментарии ниже, но я не мог полностью поймите объяснение в этой статье, поэтому я задаю этот вопрос.
7 ответов:
закрытие происходит, когда функция имеет доступ к локальной переменной из области видимости, что закончит свое выполнение.
def make_printer(msg): def printer(): print msg return printer printer = make_printer('Foo!') printer()
, когда , новый фрейм помещается в стек с скомпилированным кодом для
printer
функция как константа и значениеmsg
как местный. Затем он создает и возвращает функцию. Потому что функцияprinter
ссылкиmsg
переменная, она сохраняется после
этот вопрос уже ответилaaronasterling
, кто-то может быть заинтересован в том, как переменные хранятся под капотом.прежде чем перейти к фрагменту:
замыкания-это функции, которые наследуют переменные из своей окружающей среды. Когда вы передаете функцию обратного вызова в качестве аргумента другой функции, которая будет выполнять ввод-вывод, эта функция обратного вызова будет вызывается позже, и эта функция будет - почти волшебным образом - помнить контекст, в котором она была объявлена, вместе со всеми переменными, доступными в этом контексте.
если функция не использует свободные переменные, она не образует замыкание.
если есть другой внутренний уровень, который использует свободные переменные -- все предыдущие уровни сохраняют лексическую среду ( пример в конце )
атрибуты функция
func_closure
на python __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>,) >>>
The
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 >>>
в приведенном выше фрагменте я не печатаю msg внутри функции принтера, поэтому он не создает никаких свободных переменных. Поскольку нет свободной переменной, внутри закрытия не будет содержимого. Это именно то, что мы видим выше.
теперь я объясню еще один другой фрагмент, чтобы очистить все
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')
Итак, мы видим, что a
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' >>>
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. определите простой модифицируемый класс
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
сам, так что никакой ошибки не возникает.3. альтернативным способом, как указал @unutbu, было бы определить каждую переменную как массив (
x = [0]
) и изменить его первый элемент (x[0] += 1
). Опять же никакой ошибки не возникает, потому чтоx
сам не изменяется.4. как предложил @раксакорикофаллапаториусе, вы могли бы сделать
x
свойстваcounter
def initCounter (): def counter(): counter.x += 1 print counter.x counter.x = 0 return counter
У меня была ситуация, когда мне нужно было отдельное, но постоянное пространство имен. Я использовал классы. Иначе и быть не может. Отдельные, но постоянные имена-это закрытие.
>>> 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
у 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
The
nonlocal
ключевое слово связывает внутреннюю функцию с внешней переменной, явно упомянутой, по сути заключая его. Следовательно, более явно "закрытие".
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
Это пример того, что замыкание и как его можно использовать.
Я хотел бы предложить еще одно простое сравнение между 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
ключевое слово.