Почему вложенные в Python функции не называются замыканиями? - PullRequest
226 голосов
/ 26 октября 2010

Я видел и использовал вложенные функции в Python, и они соответствуют определению замыкания. Так почему же они называются nested functions вместо closures?

Не являются ли вложенные функции замыканиями, поскольку они не используются внешним миром?

ОБНОВЛЕНИЕ: Я читал о замыканиях, и это заставило меня задуматься над этой концепцией в отношении Python. Я искал и нашел статью, упомянутую кем-то в комментарии ниже, но я не мог полностью понять объяснение в этой статье, поэтому я задаю этот вопрос.

Ответы [ 7 ]

363 голосов
/ 26 октября 2010

Закрытие происходит, когда функция имеет доступ к локальной переменной из охватывающей области, которая завершила свое выполнение.

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, после возврата make_printer.msg - это обычная локальная переменная функции printer в этом контексте.

91 голосов
/ 03 января 2014

На этот вопрос уже ответил aaronasterling

Однако, кому-то может быть интересно узнать, как переменные хранятся под капотом.

Перед переходом к фрагменту:

Закрытия - это функции, которые наследуют переменные из окружающей их среды.Когда вы передаете функцию обратного вызова в качестве аргумента другой функции, которая будет выполнять ввод / вывод, эта функция обратного вызова будет вызвана позже, и эта функция - почти волшебным образом - запомнит контекст, в котором она была объявлена, вместе со всеми доступными переменнымив этом контексте.

  • Если функция не использует свободные переменные, она не образует замыкание.

  • Если есть другой внутренний уровень, который использует свободные переменные - все предыдущие уровни сохраняют лексическую среду (пример в конце)

  • атрибуты функции func_closure в python <3.X или <code>__closure__ в python> 3.X сохраняют свободные переменные.

  • EveryФункция в Python имеет атрибуты замыкания, но не сохраняет никакого содержимого, если нет свободных переменных.

пример: атрибутов замыкания, но внутри нет содержимого, так какнет свободной переменной.

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

Вот атрибут 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
 >>>

В приведенном выше фрагменте я не печатаю 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')

Итак, мы видим, что 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'
>>>
67 голосов
/ 09 мая 2014

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
14 голосов
/ 14 марта 2017

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

9 голосов
/ 24 июня 2014

У меня была ситуация, когда мне нужно отдельное, но постоянное пространство имен. Я использовал классы. Я не иначе. Отдельные, но постоянные имена являются замыканиями.

>>> 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
5 голосов
/ 28 января 2014
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

Это пример того, что такое замыкание и как его можно использовать.

0 голосов
/ 30 июня 2017

Я хотел бы предложить еще одно простое сравнение между примером 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.

...