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

200

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.

romano
fuente

Respuestas:

180

Para obtener una copia totalmente independiente de un objeto, puede usar 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
fuente
2
Esta respuesta se marcó como "No es una respuesta", se eliminó y no se borró. Meta discusión aquí: meta.stackoverflow.com/questions/377844/…
Aaron Hall
@AaronHall ¡Gracias por hacérmelo saber! Ciertamente, esta no es la mejor respuesta que escribí, pero estoy de acuerdo con la decisión de que no se debe eliminar por la fuerza. Lo repasaré un poco, pero como ya hay respuestas con todos los detalles (especialmente el suyo), lo mantendré breve.
Sven Marnach
70

¿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.

Te refieres a un objeto mutable entonces.

En Python 3, las listas obtienen un copymétodo (en 2, usaría 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.copy es 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 están mutados, el cambio se muestra 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, solo en la copia.

Objetos inmutables

Los objetos inmutables generalmente no necesitan copiarse. De hecho, si lo intentas, Python solo 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

Del mismo modo para cadenas:

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

y para congelados, a pesar de que tienen un copymétodo:

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

Cuando copiar objetos inmutables

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

>>> 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 el objeto interior de la copia está mutado, el original no cambia.

Objetos personalizados

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

Para hacer 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 las copias. Para disfrutar de un buen comportamiento con estructuras de datos recursivas, asegúrese de que aún no haya hecho una copia y, si la tiene, 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]
Aaron Hall
fuente
11

Copia superficial con copy.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 con copy.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.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
-2

Creo que lo siguiente debería funcionar con muchos de buen comportamiento clasificados en Python:

def copy(obj):
    return type(obj)(obj)

(Por supuesto, no estoy hablando aquí de "copias profundas", que es una historia diferente y que puede no ser un concepto muy claro: ¿qué tan profundo es lo suficientemente profundo?)

Según mis pruebas con Python 3, para objetos inmutables, como tuplas o cadenas, devuelve el mismo objeto (porque no hay necesidad de hacer una copia superficial de un objeto inmutable), pero para listas o diccionarios crea una copia superficial independiente .

Por supuesto, este método solo funciona para clases cuyos constructores se comportan en consecuencia. Posibles casos de uso: hacer una copia superficial de una clase de contenedor Python estándar.

Alexey
fuente
Eso es ordenado y todo, pero no responde la pregunta ya que su función de copia falla para las clases personalizadas y la pregunta era sobre objetos .
Jared Smith
@JaredSmith, no se dijo que la pregunta era sobre todos los objetos. Ni siquiera estaba claro si se trataba de una copia profunda o superficial (supongo que es una copia superficial habitual, pero la respuesta aceptada es sobre una profunda). En cuanto a las clases personalizadas, si son suyas, puede respetar este tipo de convención en su __init__método. Entonces, pensé que este método puede ser lo suficientemente bueno para ciertos fines. En cualquier caso, me interesarán los comentarios informativos sobre esta sugerencia.
Alexey
Considere class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argbásico como se pone. Si lo hago foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>Significa que su copyfunción está rota incluso para las clases más básicas. Nuevamente, es un buen truco (por lo tanto, no DV), pero no una respuesta.
Jared Smith
@JaredSmith, vi que hay un copy.copymétodo para hacer copias superficiales, pero, tal vez ingenuamente, me parece que debería ser responsabilidad de la clase proporcionar un "constructor de copias superficiales". En tal caso, ¿por qué no proporcionarle el mismo tipo de interfaz que dicty listhacer? Entonces, si su clase quiere asumir la responsabilidad de copiar sus objetos, ¿por qué no agregar una if isinstance(arg, type(self))cláusula __init__?
Alexey
1
Debido a que no siempre tienes control sobre las clases, utilizas la forma en que haces las que defines. Pueden, solo como un ejemplo, ser programas C que tengan enlaces de Python (por ejemplo, GTK, openalpr, partes del núcleo). Sin mencionar que incluso si tomó una biblioteca de terceros y agregó métodos de copia a cada clase, ¿cómo va a entrelazar eso en su gestión de dependencia?
Jared Smith