Можете ли вы объяснить замыкания (поскольку они относятся к Python)?
Я много читал о замыканиях и думаю, что понимаю их, но, не омрачая картины для себя и других, я надеюсь, что кто-то сможет объяснить замыкания настолько кратко и ясно, насколько это возможно. Я ищу простое объяснение, которое могло бы помочь мне понять, где и почему я хотел бы их использовать.
Ответов (13)13
Лучшее объяснение замыкания, которое я когда-либо видел, - это объяснение механизма. Это было примерно так:
Представьте свой программный стек как вырожденное дерево, в котором каждый узел имеет только одного дочернего элемента, а единственный листовой узел является контекстом вашей выполняемой в настоящее время процедуры.
Теперь ослабьте ограничение, согласно которому у каждого узла может быть только один дочерний элемент.
Если вы это сделаете, у вас может быть конструкция ('yield'), которая может возвращаться из процедуры, не отбрасывая локальный контекст (т.е. она не выталкивает его из стека при возврате). В следующий раз, когда процедура вызывается, вызов берет старый стек (дерево) фрейм и продолжает выполнение с того места, где он остановился.
Для меня «замыкания» - это функции, которые способны запоминать среду, в которой они были созданы. Эта функция позволяет вам использовать переменные или методы в замыкании, которые иначе вы не сможете использовать, потому что они больше не существуют, или они недоступны из-за области действия. Посмотрим на этот код на рубине:
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» больше не существуют. Все из-за возможности запоминания закрытия.
В 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:
И мы можем продемонстрировать использование нашего закрытия, отметив, что '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
Есть и более сложные замыкания, которые не подходят для примера частичной функции, и я продемонстрирую их дальше, когда позволит время.
все мы использовали декораторы в 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. Чтобы код функции работал, должен быть доступен кадр стека функции-создателя, которая давно ушла, резервная копия которой содержится в замыкании, которое мы можем найти вместе с объектом функции сообщения.
Вот пример закрытия 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, C# - замыкания могут использоваться для реализации (до какой степени) новых структур управления. Отсутствие анонимных блоков - одно из ограничений закрытий в Python .
Я никогда не слышал, чтобы транзакции использовались в одном контексте с объяснением, что такое закрытие, и здесь действительно нет никакой семантики транзакции.
Это называется закрытием, потому что оно «закрывает» внешнюю переменную (константу), т. Е. Это не просто функция, а оболочка среды, в которой функция была создана.
В следующем примере вызов закрытия 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
Это просто: функция, которая ссылается на переменные из содержащей области, потенциально после того, как поток управления покинул эту область. Последний бит очень полезен:
>>> 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 правильными замыканиями.
Честно говоря, я прекрасно понимаю замыкания, за исключением того, что я никогда не понимал, что именно является «закрытием» и что в нем такого «замыкания». Я рекомендую вам отказаться от поиска какой-либо логики в выборе термина.
Во всяком случае, вот мое объяснение:
def foo():
x = 3
def bar():
print x
x = 5
return bar
bar = foo()
bar() # print 5
Ключевой идеей здесь является то, что объект функции, возвращаемый из foo, сохраняет привязку к локальной переменной 'x', даже если 'x' вышел за пределы области видимости и должен быть более не функционирующим. Этот хук относится к самой переменной var, а не только к ее значению в то время, поэтому, когда вызывается bar, он печатает 5, а не 3.
Также имейте в виду, что Python 2.x имеет ограниченное закрытие: я не могу изменить 'x' внутри 'bar', потому что запись 'x = bla' объявляет локальный 'x' в bar, а не назначает 'x' из foo . Это побочный эффект объявления Python assignment =. Чтобы обойти это, 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
Вот типичный вариант использования замыканий - обратные вызовы для элементов графического интерфейса (это было бы альтернативой подклассу класса кнопки). Например, вы можете создать функцию, которая будет вызываться в ответ на нажатие кнопки и «закрывать» соответствующие переменные в родительской области, которые необходимы для обработки щелчка. Таким образом, вы можете связать довольно сложные интерфейсы с одной и той же функцией инициализации, встраивая все зависимости в замыкание.
# 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