Atribuir uma lista a um segundo nome não copia nada. Os dois nomes apontam para o mesmo objeto, então uma mudança feita por um é visível pelo outro.
Copiar tem dois níveis: uma cópia rasa cria um objeto externo novo contendo os mesmos objetos internos, e uma cópia profunda reconstrói a árvore inteira.
Atribuição não é cópia
Uma lista, dois nomes.
# assignment does not copy
original = [1, 2, 3]
alias = original
alias.append(4)
print(original, alias is original)
Ele imprime:
[1, 2, 3, 4] True
Uma cópia rasa
.copy(), list(x) e x[:] fazem a mesma coisa para uma lista.
# a shallow copy is a new outer object
shallow = original.copy() # list(original) and original[:] do the same
shallow.append(5)
print(original, shallow, shallow is original)
Ele imprime:
[1, 2, 3, 4] [1, 2, 3, 4, 5] False
A lista de fora agora é separada.
Onde a cópia rasa para
A lista nova guarda as mesmas listas internas da antiga.
# shallow means one level
rows = [[1, 2], [3, 4]]
copied = rows.copy()
copied[0].append(99)
print(rows, copied)
print('inner list shared:', rows[0] is copied[0])
Ele imprime:
[[1, 2, 99], [3, 4]] [[1, 2, 99], [3, 4]]
inner list shared: True
As duas listas de fora mostram a mudança, porque existe uma lista interna só.
Cópia profunda
copy.deepcopy percorre a estrutura e copia cada objeto que encontra.
# deepcopy copies the whole tree
import copy
rows2 = [[1, 2], [3, 4]]
deep = copy.deepcopy(rows2)
deep[0].append(99)
print(rows2, deep)
print('inner list shared:', rows2[0] is deep[0])
Ele imprime:
[[1, 2], [3, 4]] [[1, 2, 99], [3, 4]]
inner list shared: False
A mudança fica contida.
Dicionários e configuração aninhada
Esse é o caso comum do mundo real: um dicionário de configuração que algum outro código altera.
# it works on dicts and objects too
config = {'db': {'host': 'localhost', 'ports': [5432]}}
shallow_cfg = copy.copy(config)
deep_cfg = copy.deepcopy(config)
config['db']['ports'].append(5433)
print('shallow sees the change:', shallow_cfg['db']['ports'])
print('deep does not:', deep_cfg['db']['ports'])
Ele imprime:
shallow sees the change: [5432, 5433]
deep does not: [5432]
Ciclos são tratados
O deepcopy mantém um registro do que já copiou, então uma estrutura que aponta para si mesma não causa recursão infinita.
# cycles are handled
node = {'name': 'root'}
node['self'] = node
clone = copy.deepcopy(node)
print(clone['self'] is clone, clone is node)
Ele imprime:
True False
A cópia aponta para a cópia.
Uma tupla não protege o que está dentro dela
Uma tupla não pode ser reatribuída, mas as listas dentro dela ainda podem ser alteradas — e uma cópia rasa as compartilha.
# tuples of mutables are still shared by a shallow copy
data = ([1, 2], [3, 4])
shallow_t = copy.copy(data)
shallow_t[0].append(3)
print(data, shallow_t[0] is data[0])
Ele imprime:
([1, 2, 3], [3, 4]) True
Contêineres imutáveis guardam conteúdo mutável. Vale lembrar disso quando você usa uma tupla como valor padrão.
O que lembrar
-
b = acria um segundo nome, não um segundo objeto. -
Cópias rasas duplicam só o contêiner de fora.
-
copy.deepcopyduplica a árvore inteira e lida com ciclos. -
Cópia profunda é lenta. Use quando precisar, não por padrão.
Se você está fazendo cópia profunda com frequência, normalmente é sinal de que os dados deveriam ser imutáveis. Uma dataclass congelada ou uma tupla de tuplas elimina a pergunta.