copy — Duplicar Objectos¶
Propósito: | Proporciona funciones para duplicar objetos utilizando semántica de copia superficial o profunda. |
---|
El módulo copy
incluye dos funciones, copy()
y deepcopy()
, para
duplicar objetos existentes.
Copias superficiales¶
La copia superficial creada por copy()
es un contenedor nuevo poblado con
referencias a los contenidos del objeto original. Al hacer una copia
superficial de un objeto list
, una nueva list
se construye y los
elementos del objeto original se anexan a ésta.
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.copy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(' dup is my_list:', (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
Para una copia superficial, la instancia MyClass
no es duplicada, la
referencia en la lista dup
es a el mismo objeto que está en my_list
.
$ python3 copy_shallow.py
my_list: [<__main__.MyClass object at 0x1007a87b8>]
dup: [<__main__.MyClass object at 0x1007a87b8>]
dup is my_list: False
dup == my_list: True
dup[0] is my_list[0]: True
dup[0] == my_list[0]: True
Copias profundas¶
La copia profunda creada por deepcopy()
es un contenedor nuevo poblado
con copias de los contenidos del objeto original. Para hacer una copia
profunda de una list
, una nueva lista
se construye, los elementos de la
lista original se copian, y luego esas copias son anexadas a la nueva lista.
Reemplazar la llamada de copy()
con deepcopy()
hace visible la
diferencia en la salida.
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.deepcopy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(' dup is my_list:', (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
El primer elemento de la lista ya no es la misma referencia de objeto, pero cuando se comparan los dos objetos, todavía se evalúan como iguales.
$ python3 copy_deep.py
my_list: [<__main__.MyClass object at 0x1018a87b8>]
dup: [<__main__.MyClass object at 0x1018b1b70>]
dup is my_list: False
dup == my_list: True
dup[0] is my_list[0]: False
dup[0] == my_list[0]: True
Personalizando el comportamiento de copia¶
Es posible controlar cómo se hacen las copias utilizando los métodos especiales
__copy__()
y __deepcopy__()
.
__copy__()
se llama sin ningún argumento y debe devolver un copia superficial del objeto.__deepcopy__()
se llama con un diccionario memo y debe devolver una copia profunda del objeto. Cualquier atributo miembro que necesite ser copiado profundamente debe ser pasado acopy.deepcopy()
, junto con el diccionario memo, para controlar la recursión. (El diccionario memo se explica con más detalle más adelante).
El siguiente ejemplo ilustra cómo se llaman los métodos.
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
def __copy__(self):
print('__copy__()')
return MyClass(self.name)
def __deepcopy__(self, memo):
print('__deepcopy__({})'.format(memo))
return MyClass(copy.deepcopy(self.name, memo))
a = MyClass('a')
sc = copy.copy(a)
dc = copy.deepcopy(a)
El diccionario memo se utiliza para realizar un seguimiento de los valores que ya han sido copiados, para evitar recursión infinita.
$ python3 copy_hooks.py
__copy__()
__deepcopy__({})
Recursión en copia profunda¶
Para evitar problemas con la duplicación de estructuras de datos recursivas,
deepcopy()
usa un diccionario para rastrear objetos que ya han sido
copiados. Este diccionario se pasa al método __deepcopy__()
para que pueda
examinarse allí también.
El siguiente ejemplo muestra cómo una estructura de datos interconectados como
un grafo dirigido puede ayudar a proteger contra la recursión implementando
un método __deepcopy__()
.
import copy
class Graph:
def __init__(self, name, connections):
self.name = name
self.connections = connections
def add_connection(self, other):
self.connections.append(other)
def __repr__(self):
return 'Graph(name={}, id={})'.format(
self.name, id(self))
def __deepcopy__(self, memo):
print('\nCalling __deepcopy__ for {!r}'.format(self))
if self in memo:
existing = memo.get(self)
print(' Already copied to {!r}'.format(existing))
return existing
print(' Memo dictionary:')
if memo:
for k, v in memo.items():
print(' {}: {}'.format(k, v))
else:
print(' (empty)')
dup = Graph(copy.deepcopy(self.name, memo), [])
print(' Copying to new object {}'.format(dup))
memo[self] = dup
for c in self.connections:
dup.add_connection(copy.deepcopy(c, memo))
return dup
root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.add_connection(a)
root.add_connection(b)
dup = copy.deepcopy(root)
La clase Graph
incluye algunos métodos básicos de gráficos dirigidos.
Una instancia se puede inicializar con un nombre y una lista de nodos
existentes a los que está conectado. El método add_connection()
método
se usa para configurar conexiones bidireccionales. También se usa por el
operador de copia profunda.
El método __deepcopy__()
imprime mensajes para mostrar cómo es llamado, y
administra el contenido del diccionario memo según sea necesario. En lugar de
copiar toda la lista de conexiones en su totalidad, crea una nueva lista y
anexa copias de las conexiones individuales a ésta. Eso asegura que el
diccionario memo se actualiza cuando cada nuevo nodo es duplicado, y evita
problemas de recursión o copias adicionales de nodos. Como antes, el método
devuelve el objeto copiado cuando está listo.

Copia profunda para un grafo de objetos con ciclos.¶
El grafo que se muestra en the figure incluye varios ciclos, pero manejar la recursión con el diccionario memo evita que el recorrido cause un error de desbordamiento de la pila. Cuando se copia el nodo root, produce el siguiente resultado.
$ python3 copy_recursion.py
Calling __deepcopy__ for Graph(name=root, id=4314569528)
Memo dictionary:
(empty)
Copying to new object Graph(name=root, id=4315093592)
Calling __deepcopy__ for Graph(name=a, id=4314569584)
Memo dictionary:
Graph(name=root, id=4314569528): Graph(name=root,
id=4315093592)
Copying to new object Graph(name=a, id=4315094208)
Calling __deepcopy__ for Graph(name=root, id=4314569528)
Already copied to Graph(name=root, id=4315093592)
Calling __deepcopy__ for Graph(name=b, id=4315092248)
Memo dictionary:
4314569528: Graph(name=root, id=4315093592)
4315692808: [Graph(name=root, id=4314569528), Graph(name=a,
id=4314569584)]
Graph(name=root, id=4314569528): Graph(name=root,
id=4315093592)
4314569584: Graph(name=a, id=4315094208)
Graph(name=a, id=4314569584): Graph(name=a, id=4315094208)
Copying to new object Graph(name=b, id=4315177536)
La segunda vez que se encuentra el nodo root, mientras que el nodo a está
siendo copiado, __deepcopy__()
detecta la recursión y reutiliza el valor
existente del diccionario memo en lugar de crear un nuevo objeto.