O Python possui uma variedade de tipos de dados, alguns dos quais podem ser modificados após a sua criação e outros não. Quando falamos de tipos que podem ser alterados, referimo-nos a eles como tipos "mutáveis". Em contraste, aqueles que não podem ser alterados são chamados de tipos "imutáveis".
Tipos Mutáveis
Os tipos mutáveis mais comuns são lists, dicts e sets. Aqui está um exemplo de uma lista:
Com uma list, pode alterar os seus elementos, adicionar novos ou remover existentes:
fruits[1] = 'blueberry' # Muda 'banana' para 'blueberry'
fruits.append('dragonfruit') # Adiciona 'dragonfruit' ao fim da lista
print(fruits) # ['apple', 'blueberry', 'cherry', 'dragonfruit']
Tipos Imutáveis
Por outro lado, exemplos de tipos imutáveis são ints, floats, strings e tuples. Uma vez criados, não pode alterar o seu conteúdo. Aqui está um exemplo de uma string:
Se tentar alterar parte da string, o Python não o permitirá:
message[0] = 'H' # Tentar mudar 'h' para 'H' dará um TypeError
Como é que int é imutável? Não podemos mudar o valor com += ou -=?
Quando falamos sobre mutabilidade no Python, referimo-nos à capacidade de o valor real armazenado na memória poder ser alterado. Por exemplo, quando temos uma lista, podemos alterar um item nessa lista, e continua a ser a mesma lista na memória.
Inteiros no Python são imutáveis. Isto significa que o valor na memória não pode ser alterado. Se tivermos um inteiro, a = 5, e realizarmos uma operação como a += 2, pode parecer que alterámos a. No entanto, o que o Python realmente fez foi criar um novo objeto na memória com o valor de 7 e atualizar a para apontar para esse novo objeto. O 5 original não foi alterado na memória — é por isso que ints são considerados imutáveis.
a = 5
print(id(a)) # Isto imprime a localização na memória de a. Vamos supor que é 1001
a += 2
print(a) # Isto imprime 7
print(id(a)) # Isto imprimirá uma localização de memória diferente, digamos 1002
A função id em Python retorna a identidade de um objeto, que é única e constante durante a vida útil do objeto. Portanto, quando vemos que a identidade de a mudou após a operação a += 2, é um sinal claro de que a agora aponta para um novo objeto na memória. O inteiro original 5 não foi alterado, confirmando que os inteiros são realmente imutáveis em Python.
Argumentos Mutáveis em Funções
Quando passamos um objeto mutável (como uma lista) para uma função, o Python fornece à função uma referência a esse objeto. Isto significa que, se a função modificar o objeto mutável, essa modificação também será visível fora da função.
Vamos utilizar uma função que recebe uma lista e adiciona um elemento a ela:
def add_item(my_list):
my_list.append('added item') # Adiciona um novo item à lista
shopping_list = ['apples', 'bananas', 'cherries']
print(shopping_list) # Imprime: ['apples', 'bananas', 'cherries']
# Agora usamos a função para adicionar um item à nossa lista de compras
add_item(shopping_list)
print(shopping_list) # Imprime: ['apples', 'bananas', 'cherries', 'added item']
Como pode ver, quando imprimimos shopping_list após chamar a função add_item, a lista tinha um novo item. Isto aconteceu mesmo sem modificarmos diretamente shopping_list fora da função.
No entanto, é essencial compreender que, se atribuir um novo valor ao objeto mutável inteiro dentro da função, isso não afetará o objeto original. Isto porque está a atribuir uma nova referência à variável local da função, não modificando o objeto original. Vamos ver um exemplo disto:
def change_list(my_list):
my_list = ['entirely', 'new', 'list'] # Isto não afetará a lista original
shopping_list = ['apples', 'bananas', 'cherries']
change_list(shopping_list)
print(shopping_list) # Ainda imprime: ['apples', 'bananas', 'cherries']
Embora tenhamos chamado a função change_list com shopping_list como argumento, o conteúdo de shopping_list não mudou. Isto porque, dentro da função, my_list recebeu uma nova referência (para uma lista completamente nova), enquanto a referência de shopping_list permaneceu intocada.
A principal conclusão aqui é: quando passa um objeto mutável para uma função em Python, lembre-se de que, se a função alterar o objeto (como adicionar um item a uma lista), a alteração é permanente e visível fora da função. Porém, se atribuir um novo valor ao objeto inteiro dentro da função, isso não afetará o objeto original.