Pergunta

Tenho lido muito sobre encerramentos e acho que os entendo, mas sem obscurecer o quadro para mim e para os outros, espero que alguém possa explicar os encerramentos da forma mais sucinta e clara possível.Estou procurando uma explicação simples que possa me ajudar a entender onde e por que eu gostaria de usá-los.

Foi útil?

Solução

Fechamento em fechamentos

Objetos são dados com métodos anexados, os fechamentos são funções com dados anexados.

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

Outras dicas

É simples:Uma função que faz referência a variáveis ​​de um escopo contendo, potencialmente após o fluxo de controle ter deixado esse escopo.Essa última parte é muito útil:

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

Observe que 12 e 4 "desapareceram" dentro de f e g, respectivamente, esse recurso é o que torna f e g fechamentos adequados.

Eu gosto esta definição aproximada e sucinta:

Uma função que pode fazer referência a ambientes que não estão mais ativos.

eu adicionaria

Um fechamento permite vincular variáveis ​​em uma função sem passá-los como parâmetros.

Decoradores que aceitam parâmetros são um uso comum para fechamentos.Os encerramentos são um mecanismo de implementação comum para esse tipo de "fábrica de funções".Eu freqüentemente escolho usar fechamentos no Padrão de Estratégia quando a estratégia é modificada por dados em tempo de execução.

Em uma linguagem que permite a definição anônima de blocos - por exemplo, Ruby, C# - os fechamentos podem ser usados ​​para implementar (o que equivale a) novas estruturas de controle novas.A falta de blocos anônimos está entre as limitações dos fechamentos em Python.

Para ser honesto, eu entendo os fechamentos perfeitamente bem, exceto que nunca fui claro sobre o que exatamente é o "fechamento" e o que há de tão "fechamento" nisso.Recomendo que você desista de procurar qualquer lógica por trás da escolha do termo.

De qualquer forma, aqui está minha explicação:

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

bar = foo()
bar()   # print 5

Uma ideia chave aqui é que o objeto de função retornado de foo retém um gancho para a var local 'x' mesmo que 'x' tenha saído do escopo e deva estar extinto.Esse gancho é para o próprio var, não apenas para o valor que var tinha no momento; portanto, quando bar é chamado, ele imprime 5, não 3.

Também fique claro que o Python 2.x tem fechamento limitado:não há como modificar 'x' dentro de 'bar' porque escrever 'x = bla' declararia um 'x' local em bar, e não atribuiria a 'x' de foo.Este é um efeito colateral da atribuição = declaração do Python.Para contornar isso, o Python 3.0 introduz a palavra-chave 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

Nunca ouvi falar de transações sendo usadas no mesmo contexto para explicar o que é um encerramento e realmente não há nenhuma semântica de transação aqui.

É chamado de fechamento porque "fecha" a variável externa (constante) - ou seja, não é apenas uma função, mas um cerco do ambiente onde a função foi criada.

No exemplo a seguir, chamar o fechamento g após alterar x também alterará o valor de x dentro de g, já que g fecha sobre x:

x = 0

def f():
    def g(): 
        return x * 2
    return g


closure = f()
print(closure()) # 0
x = 2
print(closure()) # 4

Aqui está um caso de uso típico para encerramentos - retornos de chamada para elementos GUI (isso seria uma alternativa à subclasse da classe de botão).Por exemplo, você pode construir uma função que será chamada em resposta ao pressionamento de um botão e "fechar" as variáveis ​​relevantes no escopo pai que são necessárias para processar o clique.Dessa forma, você pode conectar interfaces bastante complicadas a partir da mesma função de inicialização, construindo todas as dependências no encerramento.

Em Python, um encerramento é uma instância de uma função que possui variáveis ​​vinculadas a ela de forma imutável.

Na verdade, o modelo de dados explica isso na sua descrição de funções' __closure__ atributo:

Nenhum ou um tupla de células que contêm ligações para as variáveis ​​livres da função.Somente leitura

Para demonstrar isso:

def enclosure(foo):
    def closure(bar):
        print(foo, bar)
    return closure

closure_instance = enclosure('foo')

Claramente, sabemos que agora temos uma função apontada pelo nome da variável closure_instance.Ostensivamente, se o chamarmos com um objeto, bar, ele deve imprimir a string, 'foo' e qualquer que seja a representação de string de bar é.

Na verdade, a string 'foo' é vinculado à instância da função, e podemos lê-lo diretamente aqui, acessando o cell_contents atributo da primeira (e única) célula na tupla do __closure__ atributo:

>>> closure_instance.__closure__[0].cell_contents
'foo'

Além disso, os objetos de célula são descritos na documentação da API C:

Os objetos "células" são usados ​​para implementar variáveis ​​referenciadas por vários escopos

E podemos demonstrar o uso do nosso encerramento, observando que 'foo' está preso na função e não muda:

>>> closure_instance('bar')
foo bar
>>> closure_instance('baz')
foo baz
>>> closure_instance('quux')
foo quux

E nada pode mudar isso:

>>> closure_instance.__closure__ = None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

Funções Parciais

O exemplo dado usa o encerramento como uma função parcial, mas se este for o nosso único objetivo, o mesmo objetivo pode ser alcançado com 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

Existem também fechamentos mais complicados que não caberiam no exemplo da função parcial, e irei demonstrá-los conforme o tempo permitir.

Aqui está um exemplo de fechamentos 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

Os critérios a serem atendidos pelos fechamentos são:

  1. Devemos ter função aninhada.
  2. A função aninhada deve referir-se ao valor definido na função envolvente.
  3. A função envolvente deve retornar a função aninhada.

# 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

Para mim, “fechamentos” são funções capazes de lembrar o ambiente em que foram criadas.Esta funcionalidade permite utilizar variáveis ​​ou métodos dentro do encerramento que, de outra forma, você não conseguiria utilizar porque não existem mais ou estão fora de alcance devido ao escopo.Vejamos este código em 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"  

funciona mesmo quando o método "multiplicar" e a variável "x" não existem mais.Tudo porque a capacidade de fechamento para lembrar.

todos nós usamos Decoradores em python.Eles são bons exemplos para mostrar o que são funções de fechamento em 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)

aqui o valor final é 12

Aqui, a função wrapper é capaz de acessar o objeto func porque o wrapper é um "fechamento lexical", ele pode acessar seus atributos pai.É por isso que é capaz de acessar o objeto func.

Gostaria de compartilhar meu exemplo e uma explicação sobre fechamentos.Fiz um exemplo em python e duas figuras para demonstrar os estados da pilha.

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!')

A saída deste código seria a seguinte:

*****      hello      #####

      good bye!    ♥♥♥

Aqui estão duas figuras para mostrar as pilhas e o fechamento anexado ao objeto de função.

quando a função é retornada do maker

quando a função é chamada mais tarde

Quando a função é chamada por meio de um parâmetro ou de uma variável não local, o código precisa de ligações de variáveis ​​locais, como margin_top, preenchimento e também a, b, n.Para garantir que o código da função funcione, o quadro de pilha da função maker que desapareceu há muito tempo deve estar acessível, cujo backup é feito no encerramento que podemos encontrar junto com o objeto de função da 'message'.

A melhor explicação que já vi para um fechamento foi explicar o mecanismo.Aconteceu mais ou menos assim:

Imagine sua pilha de programas como uma árvore degenerada onde cada nó tem apenas um filho e o nó folha único é o contexto do procedimento em execução no momento.

Agora relaxe a restrição de que cada nó pode ter apenas um filho.

Se você fizer isso, poderá ter uma construção ('yield') que pode retornar de um procedimento sem descartar o contexto local (ou seja,ele não sai da pilha quando você retorna).Na próxima vez que o procedimento for invocado, a invocação retoma o antigo quadro de pilha (árvore) e continua a execução de onde parou.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top