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 display
função dentro da say
função. A display
função é chamada de função aninhada.
Dentro da display
função, você acessa a greeting
variável a partir de seu escopo não local .
Python chama a greeting
variável de variável livre.
Quando você olha para a display
função, você realmente olha para:
- A
display
função em si. - E a variável livre
greeting
com o valor'Hello'
.
Portanto, a combinação da display
função e greeting
da 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 say
função retorna a display
função em vez de executá-la.
Além disso, quando a say
função retorna a display
função, ela na verdade retorna um fechamento:
O seguinte atribui o valor de retorno da say
funçã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 say
função executa e retorna uma função. Quando a fn
função é executada, a say
função já foi concluída.
Em outras palavras, o escopo da say
função desapareceu no momento em que a fn
função foi executada.
Como a greeting
variável pertence ao escopo da say
função, ela também deve ser destruída junto com o escopo da função.
No entanto, você ainda verá que fn
exibe o valor da message
variável.
Células Python e variáveis com escopo múltiplo
O valor da greeting
variável é compartilhado entre dois escopos de:
- A
say
função. - O encerramento
O rótulo greeting
está 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 say
funçã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 greeting
variá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 greeting
variá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_freevars
retorna a greeting
variável livre do fn
fechamento.
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 multiplier
que retorne um encerramento:
def multiplier(x):
def multiply(y):
return x * y
return multiply
Linguagem de código: Python ( python )
A multiplier
função retorna a multiplicação de dois argumentos. No entanto, ele usa um fechamento.
Segundo, chame a multiplier
funçã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 x
começ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 x
quando 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 x
no 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.