Можете ли вы объяснить замыкания (как они связаны с Python)? - PullRequest
75 голосов
/ 17 августа 2008

Я много читал о замыканиях и думаю, что понимаю их, но, не затуманив картину для себя и других, я надеюсь, что кто-то сможет объяснить замыкания максимально кратко и четко. Я ищу простое объяснение, которое могло бы помочь мне понять, где и почему я хотел бы использовать их.

Ответы [ 13 ]

85 голосов
/ 26 сентября 2008

Закрытие на закрытиях

Объекты - данные с методами прилагается, закрытия являются функциями с данные прилагаются.

def make_counter():
    i = 0
    def counter(): # counter() is a closure
        nonlocal i
        i += 1
        return i
    return counter

c1 = make_counter()
c2 = make_counter()

print (c1(), c1(), c2(), c2())
# -> 1 2 1 2
43 голосов
/ 17 августа 2008

Все просто: функция, которая ссылается на переменные из содержащей области, возможно, после того, как поток управления покинул эту область. Этот последний бит очень полезен:

>>> def makeConstantAdder(x):
...     constant = x
...     def adder(y):
...         return y + constant
...     return adder
... 
>>> f = makeConstantAdder(12)
>>> f(3)
15
>>> g = makeConstantAdder(4)
>>> g(3)
7

Обратите внимание, что 12 и 4 "исчезли" внутри f и g, соответственно, эта функция делает f и g правильными замыканиями.

14 голосов
/ 23 августа 2008

Если честно, я прекрасно понимаю замыкания, за исключением того, что я никогда не понимал, что именно является «замыканием» и что такое «замыкание» в этом. Я рекомендую вам отказаться от поисков логики выбора термина.

В любом случае, вот мое объяснение:

def foo():
   x = 3
   def bar():
      print x
   x = 5
   return bar

bar = foo()
bar()   # print 5

Ключевой идеей здесь является то, что объект функции, возвращаемый из foo, сохраняет хук к локальной переменной «x», даже если «x» вышел из области видимости и должен быть не функционирующим. Этот хук относится к самому var, а не только к значению, которое var имело в то время, поэтому, когда вызывается bar, он печатает 5, а не 3.

Также следует иметь в виду, что Python 2.x имеет ограниченное закрытие: я не могу изменить 'x' внутри 'bar', потому что написание 'x = bla' объявит локальный 'x' в баре, а не назначит 'x 'фу. Это побочный эффект Python для присваивания = объявление. Чтобы обойти это, Python 3.0 вводит нелокальное ключевое слово:

def foo():
   x = 3
   def bar():
      print x
   def ack():
      nonlocal x
      x = 7
   x = 5
   return (bar, ack)

bar, ack = foo()
ack()   # modify x of the call to foo
bar()   # print 7
14 голосов
/ 18 августа 2008

Мне нравится это грубое, краткое определение :

Функция, которая может относиться к средам, которые больше не активны.

Я бы добавил

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

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

На языке, который допускает анонимное определение блока, например, Ruby, C #, можно использовать замыкания для реализации (в каком количестве) новых новых структур управления. Отсутствие анонимных блоков входит в число ограничений замыканий в Python .

6 голосов
/ 18 августа 2008

Я никогда не слышал, чтобы транзакции использовались в том же контексте, что и объяснение, что такое закрытие, и здесь действительно нет семантики транзакций.

Это называется замыканием, потому что оно «закрывает» внешнюю переменную (константу), т. Е. Это не просто функция, а оболочка среды, в которой была создана функция.

В следующем примере вызов замыкания g после изменения x также изменит значение x в пределах g, поскольку g закрывается по x:

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4
3 голосов
/ 23 января 2009

Вот типичный вариант использования для замыканий - обратные вызовы для элементов GUI (это было бы альтернативой подклассу класса кнопки). Например, вы можете создать функцию, которая будет вызываться в ответ на нажатие кнопки, и «закрывать» соответствующие переменные в родительской области, которые необходимы для обработки щелчка. Таким образом, вы можете подключить довольно сложные интерфейсы из одной и той же функции инициализации, встроив все зависимости в замыкание.

1 голос
/ 26 декабря 2017
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

# Defining a closure

# This is an outer function.
def outer_function(message):
    # This is an inner nested function.
    def inner_function():
        print(message)
    return inner_function

# Now lets call the outer function and return value bound to name 'temp'
temp = outer_function("Hello")
# On calling temp, 'message' will be still be remembered although we had finished executing outer_function()
temp()
# Technique by which some data('message') that remembers values in enclosing scopes 
# even if they are not present in memory is called closures

# Output: Hello

Критерии, которым должны соответствовать закрытия:

  1. У нас должна быть вложенная функция.
  2. Вложенная функция должна ссылаться на значение, определенное в закрывающей функции.
  3. Включающая функция должна возвращать вложенную функцию.

# Example 2
def make_multiplier_of(n): # Outer function
    def multiplier(x): # Inner nested function
        return x * n
    return multiplier
# Multiplier of 3
times3 = make_multiplier_of(3)
# Multiplier of 5
times5 = make_multiplier_of(5)
print(times5(3)) # 15
print(times3(2)) #  6
1 голос
/ 22 сентября 2015

Вот пример закрытия Python3

def closure(x):
    def counter():
        nonlocal x
        x += 1
        return x
    return counter;

counter1 = closure(100);
counter2 = closure(200);

print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 1 " + str(counter1()))
print("i from closure 2 " + str(counter2()))

# result

i from closure 1 101
i from closure 1 102
i from closure 2 201
i from closure 1 103
i from closure 1 104
i from closure 1 105
i from closure 2 202
1 голос
/ 18 июля 2014

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

Фактически, модель данных объясняет это в описании функций __closure__ атрибут:

Нет или кортеж ячеек , которые содержат привязки для свободных переменных функции. Только для чтения

Чтобы продемонстрировать это:

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

Очевидно, мы знаем, что теперь у нас есть функция, указанная из имени переменной closure_instance. Якобы, если мы вызываем его с помощью объекта bar, он должен вывести строку 'foo' и все, что представляет собой строковое представление bar.

На самом деле строка 'foo' связана с экземпляром функции, и мы можем непосредственно прочитать ее здесь, обратившись к атрибуту cell_contents первой (и единственной) ячейки в кортеж атрибута __closure__:

>>> closure_instance.__closure__[0].cell_contents
'foo'

Кроме того, объекты ячеек описаны в документации C API:

Объекты «Cell» используются для реализации переменных, на которые ссылаются множественные прицелы

И мы можем продемонстрировать использование нашего замыкания, отметив, что 'foo' застрял в функции и не изменяется:

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

И ничто не может изменить это:

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

Частичные функции

В приведенном примере закрытие используется как частичная функция, но если это наша единственная цель, то же можно достичь с помощью functools.partial

>>> from __future__ import print_function # use this if you're in Python 2.
>>> partial_function = functools.partial(print, 'foo')
>>> partial_function('bar')
foo bar
>>> partial_function('baz')
foo baz
>>> partial_function('quux')
foo quux

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

0 голосов
/ 12 мая 2018

Я хотел бы поделиться своим примером и объяснением закрытия. Я сделал пример с Python и две цифры для демонстрации состояний стека.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n’ * margin_top, a * n, 
            ' ‘ * padding, msg, ' ‘ * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)
…
f('hello')
g(‘good bye!')

Вывод этого кода будет следующим:

*****      hello      #####

      good bye!    ♥♥♥

Вот два рисунка, на которых показаны стеки и замыкание, прикрепленные к функциональному объекту.

когда функция возвращается от создателя

когда функция вызывается позже

Когда функция вызывается через параметр или нелокальную переменную, коду нужны привязки локальных переменных, такие как margin_top, padding, а также a, b, n. Для того чтобы код функции работал, должен быть доступен стековый фрейм функции maker, который давно ушел, который поддерживается в замыкании, которое мы можем найти вместе с объектом функции 'message.

...