Você pode resolver esses três problemas (enganosamente) simples em Python?

Desde o início de minha jornada como desenvolvedor de software, eu realmente gostei de me aprofundar no interior das linguagens de programação. Eu sempre me interessei em como essa ou aquela construção funciona, como essa ou aquela equipe funciona, o que está sob o capô do açúcar sintático, etc. Recentemente, deparei com um artigo interessante com exemplos de como objetos mutáveis ​​e imutáveis ​​no Python nem sempre funcionam obviamente. Na minha opinião, a chave é como o comportamento do código muda dependendo do tipo de dados usado, mantendo as mesmas semânticas e construções de linguagem usadas. Este é um ótimo exemplo de pensamento, não apenas ao escrever, mas também ao usá-lo. Convido todos a se familiarizarem com a tradução.







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 le 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 ximutável, a operação x+=5nã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 é 7adicionado ao conjunto inicialmente vazio e resulta {7}; então o valor é 6adicionado ao conjunto {4, 5}e obtido {4, 5, 6}.



E então começam as esquisitices. O valor 2não é adicionado ao conjunto vazio, mas a {7}. Por quê? O valor inicial do parâmetro opcional é savaliado 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ávelsque 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 lainda se refere ao endereço de memória antigo. No entanto, tentar alterar uma variável imutável na segunda função y += xresulta 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:



  • .
  • - .



All Articles