Asignar una lista a un segundo nombre no copia nada. Los dos nombres apuntan al mismo objeto, así que un cambio hecho a través de uno se ve a través del otro.
Copiar tiene dos niveles: una copia superficial crea un objeto exterior nuevo que guarda los mismos objetos interiores, y una copia profunda reconstruye todo el árbol.
Asignar no es copiar
Una lista, dos nombres.
# assignment does not copy
original = [1, 2, 3]
alias = original
alias.append(4)
print(original, alias is original)
Imprime:
[1, 2, 3, 4] True
Una copia superficial
.copy(), list(x) y x[:] hacen lo mismo para una 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)
Imprime:
[1, 2, 3, 4] [1, 2, 3, 4, 5] False
La lista exterior ahora está separada.
Dónde se detiene lo superficial
La lista nueva guarda las mismas listas interiores que la vieja.
# 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])
Imprime:
[[1, 2, 99], [3, 4]] [[1, 2, 99], [3, 4]]
inner list shared: True
Las dos listas exteriores muestran el cambio, porque hay una sola lista interior.
Copia profunda
copy.deepcopy recorre la estructura y copia cada objeto que encuentra.
# 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])
Imprime:
[[1, 2], [3, 4]] [[1, 2, 99], [3, 4]]
inner list shared: False
El cambio queda contenido.
Diccionarios y configuración anidada
Este es el caso real habitual: un diccionario de configuración que otro código muta.
# 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'])
Imprime:
shallow sees the change: [5432, 5433]
deep does not: [5432]
Los ciclos están contemplados
deepcopy guarda un registro de lo que ya copió, así que una estructura que apunta a sí misma no provoca recursión infinita.
# cycles are handled
node = {'name': 'root'}
node['self'] = node
clone = copy.deepcopy(node)
print(clone['self'] is clone, clone is node)
Imprime:
True False
La copia apunta a la copia.
Una tupla no protege lo que tiene adentro
Una tupla no se puede reasignar, pero las listas que tiene adentro sí se pueden cambiar, y una copia superficial las comparte.
# 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])
Imprime:
([1, 2, 3], [3, 4]) True
Los contenedores inmutables guardan contenido mutable. Vale la pena recordarlo cuando uses una tupla como valor por defecto.
Qué recordar
-
b = acrea un segundo nombre, no un segundo objeto. -
Las copias superficiales duplican solo el contenedor exterior.
-
copy.deepcopyduplica todo el árbol y contempla los ciclos. -
Copiar en profundidad es lento. Úsalo cuando lo necesites, no por defecto.
Si estás copiando en profundidad seguido, normalmente es señal de que los datos deberían ser inmutables. Una dataclass congelada o una tupla de tuplas elimina la pregunta.