fevereiro | 2013 | Python Help
Variáveis, valores e referências
Já vi muita gente tendo problemas em código Python simplesmente por não entender direito como Python lida com os conceitos de variáveis. Considere o exemplo abaixo:
Qual o resultado esperado pela execução da última linha (print y)? Se você ficou na dúvida, mesmo que por um curto período de tempo, leia este post até o fim, que você irá entender melhor.
Grave o seguinte:
Em Python, uma variável é apenas um NOME que REFERENCIA a um OBJETO.
O código acima é muitas vezes lido como "atribui o valor 42 à variável x". Mas, o que Python faz é o seguinte: cria um objeto do tipo int que possui 42 como valor, cria o nome x e faz com que o nome x referencie o objeto (do tipo int) 42. Assim, toda vez que o nome x for usado em seu código, ele será automaticamente substituído pelo valor do objeto que este nome referencia (42). A imagem abaixo ilustra melhor a relação entre x e 42.
Continuando o exemplo anterior, o que acontece se fizermos o seguinte?
É simples, o nome x passa a fazer referência a um novo objeto do tipo int, cujo valor é 43.
A imagem a seguir dá uma idéia melhor sobre o que acontece.
Objetos do tipo int são imutáveis. x = x + 1 cria um novo objeto do tipo int (cujo valor é determinado pela soma de x com 1) e faz com que x passe a referenciar esse novo objeto. Se você observar a imagem acima, verá que não há mais seta alguma apontando para o valor 42, isto é, não há mais nenhum nome fazendo referência àquele objeto. Normalmente, um objeto que não possui nome algum o referenciando vira candidato a coleta de lixo, que é um mecanismo que elimina da memória objetos que não são mais necessários. Mas, o interpretador Python não realiza esse processo em objetos do tipo int e do tipo str (quando pequenos). Ao invés disso, ele mantém esses objetos em uma espécie de cache, para não ter que recriá-los em um futuro próximo e a todo momento em que forem necessários. Se quiser confirmar isso:
>>> 42 is 42  # ambos são o mesmo objeto
>>> 'ola' is 'ola'
>>> [] is []  # o mesmo já não vale para listas
Como comentei anteriormente, toda vez que um nome de variável aparece em uma expressão, esse nome é substituído pelo valor do objeto ao qual ele faz referência. Sabendo disso, considere a expressão abaixo:
O interpretador cria um novo nome y e faz com que ele referencie o objeto referenciado por x, como mostra a figura abaixo:
Agora, o que acontece se fizermos o seguinte?
É criado um objeto int com valor 10, e x então passa a referenciar a esse novo objeto.
E daí que entendendo isso tudo, você achará mais natural alguns comportamentos em Python. Por exemplo, teste o seguinte código e tente entender o que acontece:
Como mostra a imagem abaixo, y = x faz com que y passe a referenciar o mesmo objeto que x referencia.
Assim, x.append(4) tem efeito sobre o objeto referenciado agora pelas duas variáveis (x e y).
Isso ocorre porque listas, em Python, são objetos mutáveis. O método append() modifica a lista de modo in-place, isto é, as modificações são feitas no próprio objeto, sem a necessidade de criação de uma nova lista, como ocorreria com objetos imutáveis, como strings ou ints, por exemplo.
Vamos ver agora um exemplo da diferença entre um objeto mutável (lista) e um objeto imutável (string). Temos dois objetos, l e s:
Queremos adicionar um novo elemento ao fim de cada um deles. Com a lista podemos usar o método append():
Que adiciona o valor 4 ao final de l, modificando-a. Com a string, não temos esse método disponível, então vamos usar o operador de concatenação:
>>> s = s + 'd'
O lado direito da expressão acima cria uma nova string com o conteúdo de s acrescido do caractere 'd' e faz com que o nome s passe a referenciar essa nova string. Ou seja, ao invés de modificar, foi criado um novo objeto. O antigo valor de s ('abc') passa então a ficar sem referência alguma a ele.
Para confirmar se duas variáveis referenciam o mesmo objeto, podemos usar o operador de identidade is que verifica se duas variáveis possuem como valor o mesmo objeto.
Outra forma de verificar se duas variáveis se referem ao mesmo objeto é usando o comando id(), que retorna o identificador do objeto, que nada mais é do que um número inteiro que cada objeto possui para ser unicamente identificado:
>>> print id(y)
Tanto x quanto y se referem ao objeto com o identificador 30008456, isto é, ao mesmo objeto ([1, 2, 3]).
Agora, se o que você deseja é fazer uma cópia do objeto lista, de modo que uma modificação na cópia não interfira no objeto original, existem algumas formas de fazer isso: usando o operador de fatiamento (slicing) ou o método copy(), disponível no módulo copy.
>>> y = x[:] # cria uma nova lista com todo conteúdo de x e atribui a y
Perceba que o método copy.copy() faz apenas o que chamamos de cópia rasa da lista, pois se a lista em questão possuir outras listas aninhadas, estas não serão copiadas, sendo somente suas referências copiadas. Para cópias profundas, use copy.deepcopy().
Outra confusão muito comum é a passagem de parâmetros para funções. Veja o exemplo abaixo e tente descobrir o resultado da execução:
print 'x:', x
(Pare por um momento se for necessário antes de seguir a leitura e analise o código acima para descobrir o resultado.)
y: [1, 2, 3, 4]
A função func recebeu dois argumentos. De forma leiga, poderíamos dizer que ambos sofreram alterações dentro da função, mas somente na lista a alteração persistiu fora do escopo da função. Isso tem uma explicação bem clara: para qualquer objeto passado para uma função, é feita uma cópia da referência do objeto para o escopo local da função. Assim, se o objeto for mutável, uma operação como append(), por exemplo, vai afetar o objeto referenciado pela variável e assim a alteração persiste fora do escopo da função. Com um objeto imutável, não é possível alterar o objeto em si. O que se faz é alterar a referência (x = x + 1, por exemplo). Como a referência é apenas uma cópia, qualquer alteração feita sobre ela não irá ter efeito na referência do escopo de fora. Por isso, x permanece referenciando o mesmo objeto (1) no escopo global após a função ter terminado.
Resumindo: para entender o comportamento da passagem de parâmetros em Python, basta entender a diferença entre objetos mutáveis e imutáveis, e também lembrar que toda variável em Python nada mais é do que uma referência para algum objeto da memória.
Além disso, é conveniente deixar de lado um pouco aquela noção de que x = x + 1 (ou operações parecidas) altera(m) o objeto. Lembre sempre que o lado direito da expressão (x + 1) cria um novo objeto e que o lado esquerdo da expressão (x =) faz com que o nome x passe a referenciar o novo objeto. Mais nada. 🙂
O conceito de variáveis e objetos em Python é bem simples e consistente. Fique atento ao seu código, principalmente quando você precisa realizar cópias de objetos, o que em Python não é feito usando o operador =. Ah, não se esqueça outras linguagens implementam esses conceitos de formas diferentes. Veja: Other languages have "variables"… Python has "names" .