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

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

17.08.2008 19:14:30
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
91
25.10.2008 06:46:26
Обратите внимание, что nonlocalбыло добавлено в python 3, в python 2.x не было полноценных замыканий чтения-записи (то есть вы могли читать закрытые переменные, но не могли изменять их значения)
James Porter 20.09.2013 14:26:36
@JamesPorter: примечание: вы можете эмулировать nonlocalключевое слово в Python 2 с помощью изменяемого объекта, например, L = [0] \n def counter(): L[0] += 1; return L[0]то есть вы не можете изменить имя (связать его с другим объектом) в этом случае, но вы можете изменить сам изменяемый объект, на который ссылается имя к. Список необходим, потому что целые числа неизменны в Python.
jfs 21.09.2013 11:38:03
@JFSebastian: верно. это всегда похоже на грязный, грязный хак :)
James Porter 21.09.2013 16:24:56

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

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

45
17.08.2008 19:32:33
Там нет необходимости делать constant = x; Вы можете просто сделать это return y + xво вложенной функции (или получить аргумент с именем constant), и она будет работать нормально; аргументы захватываются замыканием не иначе, как не аргументные локальные.
ShadowRanger 12.03.2020 17:09:07

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

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

В следующем примере вызов замыкания 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
6
19.12.2015 10:43:31
Также, как есть, g()вычисляет, x * 2но ничего не возвращает. Это должно быть return x * 2. +1 тем не менее за объяснение слова "закрытие".
Bruno Le Floch 28.08.2012 00:42:04

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

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

Я бы добавил

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

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

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

14
17.08.2008 21:11:20

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

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

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' из foo , Это побочный эффект 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
12.04.2009 01:42:13

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

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

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

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

-2
18.09.2008 17:10:42
Это НЕ объяснение замыканий.
Jules 11.01.2009 22:32:22
Вы описываете продолжения, а не замыкания.
Matthew Olenik 12.04.2009 02:16:09

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

3
23.01.2009 16:13:53

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

0
18.07.2014 02:56:09

В 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

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

1
28.07.2014 04:00:32

Вот пример закрытия 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
22.09.2015 20:16:22
# 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
26.12.2017 16:41:29

мы все использовали декораторы в 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.

0
17.04.2018 19:00:31

Я хотел бы поделиться своим примером и объяснением закрытия. Я сделал пример с 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.

0
12.05.2018 04:23:09