Tente estes três problemas e verifique as respostas no final do artigo.
Dica : Os problemas têm algo em comum; portanto, retifique o primeiro problema ao passar para o segundo ou terceiro, será mais fácil para você.
Primeira tarefa
Existem várias variáveis:
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
O que será exibido ao imprimir
l
e s
?
Segunda tarefa
Defina uma função simples:
def f(x, s=set()):
s.add(x)
print(s)
O que acontece se você ligar para:
>>f(7)
>>f(6, {4, 5})
>>f(2)
Terceira tarefa
Definimos duas funções simples:
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
O que obtemos depois de executar esses comandos?
>>f_inner = f()
>>print(f_inner(2))
>>g_inner = g()
>>print(g_inner(2))
Quão confiante você está em suas respostas? Vamos verificar o seu caso.
A solução para o primeiro problema
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Por que a segunda lista responde a uma alteração em seu primeiro elemento
a.append(5)
e a primeira lista ignora completamente a mesma alteração x+=5
?
Solução do segundo problema
Vamos ver o que acontece:
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Espere, não deveria ser o último resultado
{2}
?
Solução do terceiro problema
O resultado será assim:
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Por
g_inner(2)
que ela não fez 3
? Por f()
que a função interna lembra o escopo externo, mas a função interna g()
não lembra? Eles são quase idênticos!
Explicação
E se eu lhe dissesse que todos esses comportamentos estranhos têm a ver com a diferença entre objetos mutáveis e imutáveis no Python?
Objetos mutáveis, como listas, conjuntos ou dicionários, podem ser alterados no local. Objetos imutáveis, como valores numéricos e de sequência, as tuplas não podem ser modificadas; sua "mudança" levará à criação de novos objetos.
Primeira explicação da tarefa
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Por ser
x
imutável, a operação x+=5
não altera o objeto original, mas cria um novo. Mas o primeiro elemento da lista ainda se refere ao objeto original, portanto, seu valor não muda.
Porque um objeto mutável, o comando
a.append(5)
modifica o objeto original (em vez de criar um novo) e a lista s
"vê" as alterações.
Explicação da segunda tarefa
def f(x, s=set()):
s.add(x)
print(s)
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Tudo fica claro com os dois primeiros resultados: o primeiro valor é
7
adicionado ao conjunto inicialmente vazio e resulta {7}
; então o valor é 6
adicionado ao conjunto {4, 5}
e obtido {4, 5, 6}
.
E então começam as esquisitices. O valor
2
não é adicionado ao conjunto vazio, mas a {7}. Por quê? O valor inicial do parâmetro opcional é s
avaliado apenas uma vez: na primeira chamada, s será inicializado como um conjunto vazio. E, uma vez que é mutável, após a chamada, f(7)
ela será alterada “no lugar”. A segunda chamada f(6, {4, 5})
não afetará o parâmetro padrão: o aparelho o substitui {4, 5}
, ou seja, é {4, 5}
uma variável diferente. A terceira chamada f(2)
usa a mesma variávels
que foi usado durante a primeira chamada, mas não é reinicializado como um conjunto vazio, mas seu valor anterior é obtido {7}
.
Portanto, você não deve usar argumentos mutáveis como argumentos padrão. Nesse caso, a função precisa ser alterada:
def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)
Explicação da terceira tarefa
def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner
def g():
y = 1
def inner(x):
y += x
return y
return inner
>>f_inner = f()
>>print(f_inner(2))
[1, 2]
>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment
Aqui estamos lidando com fechamentos: funções internas lembram como era o espaço para nome externo quando elas foram definidas. Ou pelo menos eles devem se lembrar, mas a segunda função faz a interface do poker e se comporta como se não tivesse ouvido falar de seu espaço de nomes externo.
Por que isso está acontecendo? Quando executamos
l.append(x)
, o objeto mutável criado quando a função é definida muda. Mas a variável l
ainda se refere ao endereço de memória antigo. No entanto, tentar alterar uma variável imutável na segunda função y += x
resulta em y começando a se referir a um endereço de memória diferente: o y original será esquecido, o que resultará em um UnboundLocalError.
Conclusão
A diferença entre objetos mutáveis e imutáveis no Python é muito importante. Evite o comportamento estranho descrito neste artigo. Especialmente:
- .
- - .