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

StackOverflow https://stackoverflow.com/questions/13857

Вопрос

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

Это было полезно?

Решение

Закрытие на крышках

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

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

Другие советы

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

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

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

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

Я бы добавил

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

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

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

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

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

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' в 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

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

В 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'

Кроме того, объекты cell описаны в документации 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

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

Вот пример замыканий 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
# 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

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

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

мы все привыкли Декораторы на языке 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!    ♥♥♥

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top