Fechamentos Python

Resumo : neste tutorial, você aprenderá sobre os encerramentos do Python e suas aplicações práticas.

Introdução aos fechamentos do Python

Em Python, você pode definir uma função dentro de outra função. E esta função é chamada de função aninhada. Por exemplo:

def say():
    greeting = 'Hello'

    def display():
        print(greeting)

    display()Linguagem de código:  Python  ( python )

Neste exemplo, definimos a displayfunção dentro da sayfunção. A displayfunção é chamada de função aninhada.

Dentro da displayfunção, você acessa a greetingvariável a partir de seu escopo não local .

Python chama a greetingvariável de variável livre.

Quando você olha para a displayfunção, você realmente olha para:

  • A displayfunção em si.
  • E a variável livre greetingcom o valor 'Hello'.

Portanto, a combinação da displayfunção e greetingda variável é chamada de encerramento:

Por definição, um fechamento é uma função aninhada que faz referência a uma ou mais variáveis ​​de seu escopo envolvente.

Retornando uma função interna

Em Python, uma função pode retornar um valor que é outra função. Por exemplo:

def say():
    greeting = 'Hello'

    def display():
        print(greeting)

    return display    
Linguagem de código:  Python  ( python )

Neste exemplo, a sayfunção retorna a displayfunção em vez de executá-la.

Além disso, quando a sayfunção retorna a displayfunção, ela na verdade retorna um fechamento:

O seguinte atribui o valor de retorno da sayfunção a uma variável fn. Como fné uma função, você pode executá-la:

fn = say()
fn()Linguagem de código:  Python  ( python )

Saída:

Hello

A sayfunção executa e retorna uma função. Quando a fnfunção é executada, a sayfunção já foi concluída.

Em outras palavras, o escopo da sayfunção desapareceu no momento em que a fnfunção foi executada.

Como a greetingvariável pertence ao escopo da sayfunção, ela também deve ser destruída junto com o escopo da função.

No entanto, você ainda verá que fnexibe o valor da messagevariável.

Células Python e variáveis ​​com escopo múltiplo

O valor da greetingvariável é compartilhado entre dois escopos de:

  • A sayfunção.
  • O encerramento

O rótulo greetingestá em dois escopos diferentes. No entanto, eles sempre fazem referência ao mesmo objeto string com o valor 'Hello'.

Para conseguir isso, Python cria um objeto intermediário chamado cell:

Para encontrar o endereço de memória do objeto de célula, você pode usar a __closure__propriedade da seguinte forma:

print(fn.__closure__)Linguagem de código:  Python  ( python )

Saída:

(<cell at 0x0000017184915C40: str object at 0x0000017186A829B0>,)Linguagem de código:  HTML, XML  ( xml )

O __closure__retorna uma tupla de células.

Neste exemplo, o endereço de memória da célula é 0x0000017184915C40. Ele faz referência a um objeto string em 0x0000017186A829B0.

Se você exibir o endereço de memória do objeto string na sayfunção e closure, verá que eles fazem referência ao mesmo objeto na memória:

def say():
    greeting = 'Hello'
    print(hex(id(greeting)))

    def display():
        print(hex(id(greeting)))
        print(greeting)

    return display


fn = say()
fn()
Linguagem de código:  Python  ( python )

Saída:

0x17186a829b0
0x17186a829b0

Quando você acessa o valor da greetingvariável, o Python tecnicamente fará um “salto duplo” para obter o valor da string.

Isso explica por que quando a say()função estava fora do escopo, você ainda pode acessar o objeto string referenciado pela greetingvariável.

Com base nesse mecanismo, você pode pensar em um encerramento como uma função e um escopo estendido que contém variáveis ​​livres.

Para encontrar as variáveis ​​livres que um encerramento contém, você pode usar o __code__.co_freevars, por exemplo:

def say():

    greeting = 'Hello'

    def display():
        print(greeting)

    return display


fn = say()
print(fn.__code__.co_freevars)Linguagem de código:  Python  ( python )

Saída:

('greeting',)Linguagem de código:  JavaScript  ( javascript )

Neste exemplo, fn.__code__.co_freevarsretorna a greetingvariável livre do fnfechamento.

Quando Python cria o fechamento

Python cria um novo escopo quando uma função é executada. Se essa função criar um fechamento, o Python também criará um novo fechamento. Considere o seguinte exemplo:

Primeiro, defina uma função chamada multiplierque retorne um encerramento:

def multiplier(x):
    def multiply(y):
        return x * y
    return multiplyLinguagem de código:  Python  ( python )

A multiplierfunção retorna a multiplicação de dois argumentos. No entanto, ele usa um fechamento.

Segundo, chame a multiplierfunção três vezes:

m1 = multiplier(1)
m2 = multiplier(2)
m3 = multiplier(3)Linguagem de código:  Python  ( python )

Essas chamadas de função criam três encerramentos. Cada função multiplica um número por 1, 2 e 3.

Terceiro, execute as funções dos encerramentos:

print(m1(10))
print(m2(10))
print(m3(10))Linguagem de código:  Python  ( python )

Saída:

10
20
30

O m1, m2 e m3 têm diferentes instâncias de fechamento.

Fechamentos Python e loop for

Suponha que você queira criar todos os três fechamentos acima de uma só vez e possa chegar ao seguinte:

multipliers = []
for x in range(1, 4):
    multipliers.append(lambda y: x * y)

m1, m2, m3 = multipliers

print(m1(10))
print(m2(10))
print(m3(10))
Linguagem de código:  PHP  ( php )

Como funciona.

  • Primeiro, declare uma lista que armazenará os encerramentos.
  • Segundo, use uma expressão lambda para criar um encerramento e anexe-o à lista em cada iteração.
  • Terceiro, descompacte os encerramentos da lista para as variáveis ​​m1, m2 e m3.
  • Por fim, passe os valores 10, 20 e 30 para cada fechamento e execute-o.

O seguinte mostra a saída:

30
30
30

Isso não funciona como você esperava. Mas por que?

O xcomeça de 1 a 3 no loop. Após o loop, seu valor é 3.

Cada elemento da lista é o seguinte fechamento:

lambda y: x*y

Python avalia xquando você chama m1(10), m2(10)e m3(10). No momento em que os fechamentos são executados, xé 3.

É por isso que você vê o mesmo resultado quando chama m1(10), m2(10)e m3(10).

Para corrigir isso, você precisa instruir o Python a avaliar xno loop:

def multiplier(x):
    def multiply(y):
        return x * y
    return multiply


multipliers = []
for x in range(1, 4):
    multipliers.append(multiplier(x))

m1, m2, m3 = multipliers

print(m1(10))
print(m2(10))
print(m3(10))
Linguagem de código:  Python  ( python )

Resumo

  • Um encerramento é uma função e um escopo estendido que contém variáveis ​​livres.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *