¿Cómo puedo crear una copia de un objeto en Python?

Resuelto Roman asked hace 13 años • 4 respuestas

Me gustaría crear una copia de un objeto. Quiero que el nuevo objeto posea todas las propiedades del objeto antiguo (valores de los campos). Pero quiero tener objetos independientes. Entonces, si cambio los valores de los campos del nuevo objeto, el objeto antiguo no debería verse afectado por eso.

Roman avatar Jan 25 '11 20:01 Roman
Aceptado

Para obtener una copia totalmente independiente de un objeto, puede utilizar la copy.deepcopy()función.

Para obtener más detalles sobre la copia superficial y profunda, consulte las otras respuestas a esta pregunta y la buena explicación en esta respuesta a una pregunta relacionada .

Sven Marnach avatar Jan 25 '2011 13:01 Sven Marnach

¿Cómo puedo crear una copia de un objeto en Python?

Entonces, si cambio los valores de los campos del nuevo objeto, el objeto antiguo no debería verse afectado por eso.

Entonces te refieres a un objeto mutable.

En Python 3, las listas obtienen un copymétodo (en 2, usarías un segmento para hacer una copia):

>>> a_list = list('abc')
>>> a_copy_of_a_list = a_list.copy()
>>> a_copy_of_a_list is a_list
False
>>> a_copy_of_a_list == a_list
True

Copias superficiales

Las copias superficiales son solo copias del contenedor más externo.

list.copyes una copia superficial:

>>> list_of_dict_of_set = [{'foo': set('abc')}]
>>> lodos_copy = list_of_dict_of_set.copy()
>>> lodos_copy[0]['foo'].pop()
'c'
>>> lodos_copy
[{'foo': {'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

No obtienes una copia de los objetos interiores. Son el mismo objeto, por lo que cuando mutan, el cambio aparece en ambos contenedores.

Copias profundas

Las copias profundas son copias recursivas de cada objeto interior.

>>> lodos_deep_copy = copy.deepcopy(list_of_dict_of_set)
>>> lodos_deep_copy[0]['foo'].add('c')
>>> lodos_deep_copy
[{'foo': {'c', 'b', 'a'}}]
>>> list_of_dict_of_set
[{'foo': {'b', 'a'}}]

Los cambios no se reflejan en el original, sólo en la copia.

Objetos inmutables

Por lo general, no es necesario copiar los objetos inmutables. De hecho, si lo intentas, Python simplemente te dará el objeto original:

>>> a_tuple = tuple('abc')
>>> tuple_copy_attempt = a_tuple.copy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'copy'

Las tuplas ni siquiera tienen un método de copia, así que probémoslo con un segmento:

>>> tuple_copy_attempt = a_tuple[:]

Pero vemos que es el mismo objeto:

>>> tuple_copy_attempt is a_tuple
True

De manera similar para las cadenas:

>>> s = 'abc'
>>> s0 = s[:]
>>> s == s0
True
>>> s is s0
True

y para frozensets, aunque tienen un copymétodo:

>>> a_frozenset = frozenset('abc')
>>> frozenset_copy_attempt = a_frozenset.copy()
>>> frozenset_copy_attempt is a_frozenset
True

Cuándo copiar objetos inmutables

Los objetos inmutables deben copiarse si necesita copiar un objeto interior mutable.

>>> tuple_of_list = [],
>>> copy_of_tuple_of_list = tuple_of_list[:]
>>> copy_of_tuple_of_list[0].append('a')
>>> copy_of_tuple_of_list
(['a'],)
>>> tuple_of_list
(['a'],)
>>> deepcopy_of_tuple_of_list = copy.deepcopy(tuple_of_list)
>>> deepcopy_of_tuple_of_list[0].append('b')
>>> deepcopy_of_tuple_of_list
(['a', 'b'],)
>>> tuple_of_list
(['a'],)

Como podemos ver, cuando se muta el objeto interior de la copia, el original no cambia .

Objetos personalizados

Los objetos personalizados generalmente almacenan datos en un __dict__atributo o en __slots__(una estructura de memoria similar a una tupla).

Para crear un objeto copiable, defina __copy__(para copias superficiales) y/o __deepcopy__(para copias profundas).

from copy import copy, deepcopy

class Copyable:
    __slots__ = 'a', '__dict__'
    def __init__(self, a, b):
        self.a, self.b = a, b
    def __copy__(self):
        return type(self)(self.a, self.b)
    def __deepcopy__(self, memo): # memo is a dict of id's to copies
        id_self = id(self)        # memoization avoids unnecesary recursion
        _copy = memo.get(id_self)
        if _copy is None:
            _copy = type(self)(
                deepcopy(self.a, memo), 
                deepcopy(self.b, memo))
            memo[id_self] = _copy 
        return _copy

Tenga en cuenta que deepcopymantiene un diccionario de memorización de id(original)(o números de identidad) para copiar. Para disfrutar de un buen comportamiento con estructuras de datos recursivas, asegúrese de no haber hecho una copia y, si la ha hecho, devuélvala.

Entonces hagamos un objeto:

>>> c1 = Copyable(1, [2])

Y copyhace una copia superficial:

>>> c2 = copy(c1)
>>> c1 is c2
False
>>> c2.b.append(3)
>>> c1.b
[2, 3]

Y deepcopyahora hace una copia profunda:

>>> c3 = deepcopy(c1)
>>> c3.b.append(4)
>>> c1.b
[2, 3]
Russia Must Remove Putin avatar Oct 25 '2017 18:10 Russia Must Remove Putin

Copia superficial concopy.copy()

#!/usr/bin/env python3

import copy

class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]

# It copies.
c = C()
d = copy.copy(c)
d.x = [3]
assert c.x == [1]
assert d.x == [3]

# It's shallow.
c = C()
d = copy.copy(c)
d.x[0] = 3
assert c.x == [3]
assert d.x == [3]

Copia profunda concopy.deepcopy()

#!/usr/bin/env python3
import copy
class C():
    def __init__(self):
        self.x = [1]
        self.y = [2]
c = C()
d = copy.deepcopy(c)
d.x[0] = 3
assert c.x == [1]
assert d.x == [3]

Documentación: https://docs.python.org/3/library/copy.html

Probado en Python 3.6.5.