Можете ли вы объяснить замыкания (поскольку они относятся к Python)?

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


person knowncitizen    schedule 17.08.2008    source источник


Ответы (13)


Закрытие при закрытии

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

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
person jfs    schedule 26.09.2008
comment
Обратите внимание, что nonlocal был добавлен в python 3, python 2.x не имел полноценных закрытий для чтения и записи (т.е. вы могли читать закрытые переменные, но не изменять их значения) - person James Porter; 20.09.2013
comment
@JamesPorter: примечание: вы можете эмулировать ключевое слово nonlocal в Python 2, используя изменяемый объект, например, L = [0] \n def counter(): L[0] += 1; return L[0], то есть вы не можете изменить имя (привязать его к другому объекту) в этом случае, но вы можете изменить сам изменяемый объект, на который ссылается имя. Список необходим, потому что целые числа неизменяемы в Python. - person jfs; 21.09.2013
comment
@ J.F.Sebastian: верно. это всегда похоже на грязный, грязный взлом :) - person James Porter; 21.09.2013

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

>>> 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 правильными замыканиями.

person Anders Eurenius    schedule 17.08.2008
comment
Нет необходимости делать constant = x; вы можете просто сделать return y + x во вложенной функции (или получить аргумент с именем constant), и все будет работать нормально; аргументы захватываются замыканием не иначе, как локальные переменные без аргументов. - person ShadowRanger; 12.03.2020

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

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

я бы добавил

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

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

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

person ESV    schedule 17.08.2008

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

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

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

bar = foo()
bar()   # print 5

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

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

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
person Jegschemesch    schedule 23.08.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
person Mark Cidade    schedule 17.08.2008
comment
Кроме того, в его нынешнем виде g() вычисляет x * 2, но ничего не возвращает. Это должно быть return x * 2. +1 тем не менее за объяснение слова «закрытие». - person Bruno Le Floch; 28.08.2012

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

person Community    schedule 23.01.2009

В 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

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

person Aaron Hall    schedule 18.07.2014

# 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
person Dinesh Sonachalam    schedule 26.12.2017

Вот пример закрытия 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
person thiagoh    schedule 22.09.2015

Для меня «замыкания» — это функции, способные запоминать среду, в которой они были созданы. Эта функциональность позволяет вам использовать переменные или методы внутри замыкания, которые, в противном случае, вы не сможете использовать либо потому, что они больше не существуют, либо они недоступны из-за области действия. Давайте посмотрим на этот код в ruby:

def makefunction (x)
  def multiply (a,b)
    puts a*b
  end
  return lambda {|n| multiply(n,x)} # => returning a closure
end

func = makefunction(2) # => we capture the closure
func.call(6)    # => Result equal "12"  

он работает, даже если и метод «умножения», и переменная «x» больше не существуют. Все потому, что замыкание способно запомнить.

person Ricardo Avila    schedule 20.09.2013

мы все использовали Decorators в Python. Это хорошие примеры, показывающие, что такое замыкающие функции в Python.

class Test():
    def decorator(func):
        def wrapper(*args):
            b = args[1] + 5
            return func(b)
        return wrapper

@decorator
def foo(val):
    print val + 2

obj = Test()
obj.foo(5)

здесь конечное значение равно 12

Здесь функция-оболочка может получить доступ к объекту func, поскольку оболочка является «лексическим закрытием», она может получить доступ к своим родительским атрибутам. Вот почему он может получить доступ к объекту func.

person Nitish Chauhan    schedule 17.04.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. Чтобы код функции работал, фрейм стека функции-создателя, который давно исчез, должен быть доступен, что подтверждается замыканием, которое мы можем найти вместе с объектом функции «сообщение».

person Eunjung Lee    schedule 12.05.2018

Лучшее объяснение закрытия, которое я когда-либо видел, было объяснение механизма. Это было примерно так:

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

Теперь ослабьте ограничение, согласно которому у каждого узла может быть только один потомок.

Если вы сделаете это, у вас может быть конструкция («выход»), которая может возвращаться из процедуры, не отбрасывая локальный контекст (т. е. она не извлекает ее из стека при возврате). При следующем вызове процедуры вызов берет старый кадр стека (дерева) и продолжает выполнение с того места, где оно было остановлено.

person Community    schedule 18.09.2008
comment
Это НЕ объяснение закрытия. - person Jules; 12.01.2009
comment
Вы описываете продолжение, а не закрытие. - person Matthew Olenik; 12.04.2009