O operador de atribuição em Python cria referências aos objetos
Python distingue entre tipos de dados mutáveis e imutáveis:
- Tipos mutáveis: listas e dicionários
- Tipos imutáveis: strings, números inteiros, números decimais e tuplas
Comportamento dos tipos imutáveis
Para dados imutáveis, o endereço de memória permanece constante durante toda a existência do objeto.
# Exemplo com número inteiro:
endereco_a = id(42)
print(f"Endereço de memória: {endereco_a}")
# Resultado: 140716627437504
endereco_b = id(42)
print(f"Endereço de memória: {endereco_b}")
# Resultado: 140716627437504
Para tipos imutáveis, não há diferença entre atribuição simples, cópia rasa ou cópia profunda, pois o conteúdo nunca é modificado in-place.
x = 100
y = x
print(f"x={x}, Endereço x: {id(x)}")
print(f"y={y}, Endereço y: {id(y)}")
# Resultado:
# x=100, Endereço x: 140716627441152
# y=100, Endereço y: 140716627441152
Comportamento dos tipos mutáveis
Objetos mutáveis criam novos endereços de memória a cada criação, mesmo com valores идентичных.
# Duas listas com mesmo conteúdo possuem endereços diferentes:
endereco_primeiro = id([10, 20])
print(f"Primeiro: {endereco_primeiro}")
# Resultado: 2234567890123
endereco_segundo = id([10, 20])
print(f"Segundo: {endereco_segundo}")
# Resultado: 2234567890456
Para referências independentes de objetos mutáveis, utiliza-se cópia rasa ou profunda.
# Atribuição simples - compartilhamento de referência:
dados = [1, 2, 3, ['x', 'y']]
referencia = dados
print(f"dados: {dados}")
print(f"dados Endereço: {id(dados)}")
print(f"referencia: {referencia}")
print(f"referencia Endereço: {id(referencia)}")
print("=" * 20)
dados.extend(referencia)
print(f"dados: {dados}")
print(f"dados Endereço: {id(dados)}")
print(f"referencia: {referencia}")
print(f"referencia Endereço: {id(referencia)}")
# Resultado:
# dados: [1, 2, 3, ['x', 'y']]
# dados Endereço: 2234567890100
# referencia: [1, 2, 3, ['x', 'y']]
# referencia Endereço: 2234567890100
# ====================
# dados: [1, 2, 3, ['x', 'y'], 1, 2, 3, ['x', 'y']]
# dados Endereço: 2234567890100
# referencia: [1, 2, 3, ['x', 'y'], 1, 2, 3, ['x', 'y']]
# referencia Endereço: 2234567890100
Cópia Superficial (Shallow Copy)
O método copy() cria um novo objeto, porém mantém referências aos objetos aninhados originais.
# Cenário 1: Operações no objeto principal não afetam a cópia
numeros = [5, 10, 15, ['a', 'b']]
copia = numeros.copy()
numeros.extend(copia)
print(f"numeros: {numeros}")
print(f"numeros Endereço: {id(numeros)}")
print(f"copia: {copia}")
print(f"copia Endereço: {id(copia)}")
# Resultado:
# numeros: [5, 10, 15, ['a', 'b'], 5, 10, 15, ['a', 'b']]
# numeros Endereço: 2234567890200
# copia: [5, 10, 15, ['a', 'b']]
# copia Endereço: 2234567890300
# Cenário 2: Modificações em objetos aninhados afetam ambos
valores = [5, 10, 15, ['a', 'b']]
copia = valores.copy()
print(f"valores: {valores}")
print(f"valores Endereço: {id(valores)}")
print(f"copia: {copia}")
print(f"copia Endereço: {id(copia)}")
print("=" * 20)
valores.extend(copia)
valores[3].extend(['m', 'n'])
print(f"valores: {valores}")
print(f"valores Endereço: {id(valores)}")
print(f"copia: {copia}")
print(f"copia Endereço: {id(copia)}")
# Resultado:
# valores: [5, 10, 15, ['a', 'b', 'm', 'n'], 5, 10, 15, ['a', 'b', 'm', 'n']]
# valores Endereço: 2234567890200
# copia: [5, 10, 15, ['a', 'b', 'm', 'n']]
# copia Endereço: 2234567890300
Cópia Profunad (Deep Copy)
O método deepcopy() do módulo copy cria uma cópia completamente independente, inculindo todos os objetos aninhados.
import copy
elementos = [5, 10, 15, ['a', 'b']]
copia_completa = copy.deepcopy(elementos)
elementos.extend(copia_completa)
elementos[3].extend(['m', 'n'])
print(f"elementos: {elementos}")
print(f"elementos Endereço: {id(elementos)}")
print(f"copia_completa: {copia_completa}")
print(f"copia_completa Endereço: {id(copia_completa)}")
# Resultado:
# elementos: [5, 10, 15, ['a', 'b', 'm', 'n'], 5, 10, 15, ['a', 'b']]
# elementos Endereço: 2234567890400
# copia_completa: [5, 10, 15, ['a', 'b']]
# copia_completa Endereço: 2234567890500
Quando utilizar cada tipo
A cópia superficial é suficiente quando:
- A estrutura de dados contém apenas tipos imutáveis
- Você precisa de desempenho otimizado e a estrutura é simples
A cópia profunda é recomendada quando:
- A estrutura possui objetos aninhados mutáveis
- É necesssário isolamento completo entre os dados originais e a cópia
- A memória disponível permite o uso de cópias independentes
Recomenda-se utilizar cópia profunda sempre que possível, pois garante independência total dos dados e evita comportamentos inesperados em alterações subsequentes.