Я много читал о замыканиях и думаю, что понимаю их, но, не затуманивая картину для себя и других, я надеюсь, что кто-то сможет объяснить замыкания максимально кратко и ясно. Я ищу простое объяснение, которое могло бы помочь мне понять, где и почему я хотел бы их использовать.
Можете ли вы объяснить замыкания (поскольку они относятся к Python)?
Ответы (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
nonlocal
был добавлен в python 3, python 2.x не имел полноценных закрытий для чтения и записи (т.е. вы могли читать закрытые переменные, но не изменять их значения)
- person James Porter; 20.09.2013
nonlocal
в Python 2, используя изменяемый объект, например, L = [0] \n def counter(): L[0] += 1; return L[0]
, то есть вы не можете изменить имя (привязать его к другому объекту) в этом случае, но вы можете изменить сам изменяемый объект, на который ссылается имя. Список необходим, потому что целые числа неизменяемы в Python.
- person jfs; 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 правильными замыканиями.
constant = x
; вы можете просто сделать return y + x
во вложенной функции (или получить аргумент с именем constant
), и все будет работать нормально; аргументы захватываются замыканием не иначе, как локальные переменные без аргументов.
- person ShadowRanger; 12.03.2020
Мне нравится это грубое и лаконичное определение:
Функция, которая может относиться к средам, которые больше не активны.
я бы добавил
Замыкание позволяет привязать переменные к функции, не передавая их в качестве параметров.
Декораторы, которые принимают параметры, часто используются для замыканий. Замыкания являются распространенным механизмом реализации для такого рода «фабрики функций». Я часто предпочитаю использовать замыкания в шаблоне стратегии, когда стратегия изменяется данными во время выполнения. .
В языке, который допускает анонимное определение блока — например, Ruby, C# — замыкания могут использоваться для реализации (в какой степени) новых новых управляющих структур. Отсутствие анонимных блоков входит в число ограничений замыканий в Python.
Честно говоря, я прекрасно понимаю замыкания, за исключением того, что мне никогда не было ясно, что именно является «замыканием» и что в этом такого «замыкания». Я рекомендую вам отказаться от поиска какой-либо логики в выборе термина.
Во всяком случае, вот мое объяснение:
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
Я никогда не слышал, чтобы транзакции использовались в том же контексте, что и объяснение того, что такое замыкание, и здесь действительно нет никакой семантики транзакций.
Это называется замыканием, потому что оно «закрывает» внешнюю переменную (константу) — т. е. это не просто функция, а оболочка среды, в которой функция была создана.
В следующем примере вызов замыкания 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
g()
вычисляет x * 2
, но ничего не возвращает. Это должно быть return x * 2
. +1 тем не менее за объяснение слова «закрытие».
- person Bruno Le Floch; 28.08.2012
Вот типичный вариант использования замыканий — обратные вызовы для элементов GUI (это была бы альтернатива подклассу класса кнопки). Например, вы можете создать функцию, которая будет вызываться в ответ на нажатие кнопки, и «закрыть» соответствующие переменные в родительской области, необходимые для обработки нажатия. Таким образом, вы можете подключать довольно сложные интерфейсы из одной и той же функции инициализации, встраивая все зависимости в замыкание.
В 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
Есть и более сложные замыкания, которые не подходят для примера частичной функции, и я продемонстрирую их дальше, когда позволит время.
# 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
Критерии, которым должны соответствовать закрытия:
- У нас должна быть вложенная функция.
- Вложенная функция должна ссылаться на значение, определенное во внешней функции.
- Охватывающая функция должна возвращать вложенную функцию.
# 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
Вот пример закрытия 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
Для меня «замыкания» — это функции, способные запоминать среду, в которой они были созданы. Эта функциональность позволяет вам использовать переменные или методы внутри замыкания, которые, в противном случае, вы не сможете использовать либо потому, что они больше не существуют, либо они недоступны из-за области действия. Давайте посмотрим на этот код в 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» больше не существуют. Все потому, что замыкание способно запомнить.
мы все использовали 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.
Я хотел бы поделиться своим примером и объяснением закрытия. Я сделал пример на 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. Чтобы код функции работал, фрейм стека функции-создателя, который давно исчез, должен быть доступен, что подтверждается замыканием, которое мы можем найти вместе с объектом функции «сообщение».
Лучшее объяснение закрытия, которое я когда-либо видел, было объяснение механизма. Это было примерно так:
Представьте стек вашей программы в виде вырожденного дерева, в котором каждый узел имеет только одного дочернего элемента, а единственный конечный узел является контекстом выполняемой в данный момент процедуры.
Теперь ослабьте ограничение, согласно которому у каждого узла может быть только один потомок.
Если вы сделаете это, у вас может быть конструкция («выход»), которая может возвращаться из процедуры, не отбрасывая локальный контекст (т. е. она не извлекает ее из стека при возврате). При следующем вызове процедуры вызов берет старый кадр стека (дерева) и продолжает выполнение с того места, где оно было остановлено.