Você pode explicar encerramentos (como eles se relacionam com Python)?


83

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.

Respostas:


95

Fechamento em fechamentos

Objetos são dados com métodos anexados, 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

6
Observe que nonlocalfoi adicionado em python 3, python 2.x não tinha fechamentos completos de leitura e gravação (ou seja, você podia ler variáveis ​​fechadas, mas não alterar seus valores)
James Porter

6
@JamesPorter: observação: você pode emular a nonlocalpalavra-chave no Python 2 usando um objeto mutável, por exemplo, L = [0] \n def counter(): L[0] += 1; return L[0]você não pode alterar o nome (vinculá-lo a outro objeto) neste caso, mas pode alterar o próprio objeto mutável ao qual o nome se refere para. A lista é necessária porque os inteiros são imutáveis ​​em Python.
jfs

1
@JFSebastian: certo. que sempre parece um hack sujo, sujo :)
James Porter

46

É simples: uma função que faz referência a variáveis ​​de um escopo contido, potencialmente depois que o fluxo de controle deixou 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 feg, respectivamente; esse recurso é o que torna feg os fechamentos adequados.


Não há necessidade de fazer constant = x; você poderia apenas fazer return y + xna função aninhada (ou receber o argumento com o nome constant) e funcionaria muito bem; os argumentos são capturados pelo encerramento da mesma forma que os locais sem argumento.
ShadowRanger

15

Eu gosto desta definição áspera e sucinta :

Uma função que pode se referir a ambientes que não estão mais ativos.

Eu adicionaria

Um encerramento permite vincular variáveis ​​em uma função sem transmiti-las como parâmetros .

Decoradores que aceitam parâmetros são um uso comum para fechamentos. Fechamentos são um mecanismo de implementação comum para esse tipo de "fábrica de funções". Eu frequentemente 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 de bloco anônimo - por exemplo, Ruby, C # - os fechamentos podem ser usados ​​para implementar (o que significa) novas estruturas de controle. A falta de blocos anônimos está entre as limitações de fechamentos em Python .


15

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

Enfim, 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', embora 'x' tenha saído do escopo e deva estar extinto. Esse gancho é para o var em si, não apenas 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 atribuir a 'x' de foo . Este é um efeito colateral da declaração de atribuição = do Python. Para contornar isso, Python 3.0 apresenta a palavra-chave não local:

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

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 sobre" a variável externa (constante) - ou seja, não é apenas uma função, mas um fechamento do ambiente onde a função foi criada.

No exemplo a seguir, chamar o fechamento g depois de alterar x também mudará 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

Além disso, como está, g()calcula , x * 2mas não retorna nada. Isso deveria ser return x * 2. 1, no entanto, para uma explicação para a palavra "encerramento".
Bruno Le Floch

3

Aqui está um caso de uso típico para encerramentos - retornos de chamada para elementos da GUI (isso seria uma alternativa para criar uma subclasse da classe do botão). Por exemplo, você pode construir uma função que será chamada em resposta ao pressionamento de um botão e "fechar" sobre 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, criando todas as dependências no encerramento.


2

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 em sua descrição do __closure__atributo de funções :

Nenhum ou uma 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 a partir do 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 da string debar .

Na verdade, a string 'foo' está ligada à instância da função e podemos lê-la diretamente aqui, acessando o cell_contentsatributo da primeira (e única) célula na tupla do __closure__atributo:

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

À parte, os objetos de célula são descritos na documentação da API C:

Objetos de "célula" são usados ​​para implementar variáveis ​​referenciadas por múltiplos escopos

E podemos demonstrar o uso de nosso fechamento, 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 fechamento como uma função parcial, mas se este for 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 encerramentos mais complicados que não caberiam no exemplo de função parcial e irei demonstrá-los posteriormente, conforme o tempo permitir.


2
# 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 se referir 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

1

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

0

Para mim, "closures" são funções capazes de lembrar o ambiente em que foram criadas. Esta funcionalidade permite que você use variáveis ​​ou métodos dentro do fechamento que, de outra forma, você não conseguiria usar 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"  

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


0

todos nós usamos decoradores em python. Eles são bons exemplos para mostrar o que são funções de encerramento 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 é "fechamento lexical", ela pode acessar seus atributos pai. Por isso, ele é capaz de acessar o objeto func.


0

Eu gostaria de compartilhar meu exemplo e uma explicação sobre fechamentos. Fiz um exemplo python e duas figuras para demonstrar estados de 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 fabricante

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. A fim de garantir que o código de função funcione, o frame da pilha da função maker que se foi há muito tempo deve estar acessível, o que é feito backup no encerramento que podemos encontrar junto com o objeto de função da mensagem.


-2

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

Imagine sua pilha de programa 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, você pode ter uma construção ('rendimento') que pode retornar de um procedimento sem descartar o contexto local (ou seja, não o retira da pilha quando você retorna). Na próxima vez que o procedimento for invocado, a invocação pega o quadro da pilha (árvore) antigo e continua executando de onde parou.


Isso NÃO é uma explicação para encerramentos.
Jules

Você está descrevendo continuações, não fechamentos.
Matthew Olenik
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.