Como posso criar uma cópia de um objeto em Python?

199

Eu gostaria de criar uma cópia de um objeto. Eu quero que o novo objeto possua todas as propriedades do objeto antigo (valores dos campos). Mas eu quero ter objetos independentes. Portanto, se eu alterar os valores dos campos do novo objeto, o objeto antigo não deverá ser afetado por isso.

romano
fonte

Respostas:

179

Para obter uma cópia totalmente independente de um objeto, você pode usar a copy.deepcopy()função

Para obter mais detalhes sobre cópia superficial e profunda, consulte as outras respostas a esta pergunta e a boa explicação nesta resposta a uma pergunta relacionada .

Sven Marnach
fonte
2
Esta resposta foi sinalizada como "Não é uma resposta", excluída e eliminada - meta-discussão aqui: meta.stackoverflow.com/questions/377844/…
Aaron Hall
@AaronHall Obrigado por me avisar! Essa certamente não é a melhor resposta que escrevi, mas concordo com a decisão de que ela não deve ser excluída à força. Vou melhorar um pouco, mas como já existem respostas com todos os detalhes (principalmente os seus), vou mantê-lo breve.
Sven Marnach
70

Como posso criar uma cópia de um objeto em Python?

Portanto, se eu alterar os valores dos campos do novo objeto, o objeto antigo não deverá ser afetado por isso.

Você quer dizer um objeto mutável então.

No Python 3, as listas obtêm um copymétodo (em 2, você usaria uma fatia para fazer uma cópia):

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

Cópias rasas

Cópias rasas são apenas cópias do recipiente mais externo.

list.copy é uma cópia 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'}}]

Você não recebe uma cópia dos objetos internos. Eles são o mesmo objeto - portanto, quando são mutados, a alteração aparece nos dois contêineres.

Cópias profundas

Cópias profundas são cópias 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'}}]

As alterações não são refletidas no original, apenas na cópia.

Objetos imutáveis

Objetos imutáveis ​​geralmente não precisam ser copiados. De fato, se você tentar, o Python fornecerá apenas o 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'

As tuplas nem sequer têm um método de cópia, então vamos tentar com uma fatia:

>>> tuple_copy_attempt = a_tuple[:]

Mas vemos que é o mesmo objeto:

>>> tuple_copy_attempt is a_tuple
True

Da mesma forma para seqüências de caracteres:

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

e para frozensets, mesmo que eles tenham um copymétodo:

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

Quando copiar objetos imutáveis

Objetos imutáveis devem ser copiados se você precisar de um objeto interior mutável 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, quando o objeto interior da cópia é alterado , o original não muda.

Objetos personalizados

Objetos personalizados geralmente armazenam dados em um __dict__atributo ou em __slots__(uma estrutura de memória semelhante a uma tupla).

Para criar um objeto copiável, defina __copy__(para cópias rasas) e / ou __deepcopy__(para cópias 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

Observe que deepcopymantém um dicionário de memorização id(original)(ou números de identidade) para cópias. Para ter bom comportamento com estruturas de dados recursivas, verifique se você ainda não fez uma cópia e, se tiver, devolva-a.

Então, vamos criar um objeto:

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

E copyfaz uma cópia superficial:

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

E deepcopyagora faz uma cópia profunda:

>>> c3 = deepcopy(c1)
>>> c3.b.append(4)
>>> c1.b
[2, 3]
Aaron Hall
fonte
10

Cópia rasa com 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]

Cópia profunda com 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]

Documentação: https://docs.python.org/3/library/copy.html

Testado em Python 3.6.5.

Ciro Santilli adicionou uma nova foto
fonte
-2

Eu acredito que o seguinte deve funcionar com muitos bem-comportados classificados em Python:

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

(É claro que não estou falando aqui de "cópias profundas", que é uma história diferente e que pode não ser um conceito muito claro - quão profunda é a profundidade suficiente?)

De acordo com meus testes com o Python 3, para objetos imutáveis, como tuplas ou cordas, ele retorna o mesmo objeto (porque não há necessidade de fazer uma cópia superficial de um objeto imutável), mas para listas ou dicionários ele cria uma cópia superficial independente .

Obviamente, esse método funciona apenas para classes cujos construtores se comportam de acordo. Possíveis casos de uso: fazendo uma cópia superficial de uma classe de contêiner Python padrão.

Alexey
fonte
Isso é legal e tudo, mas não responde à pergunta, pois sua função de cópia falha nas classes personalizadas e a pergunta era sobre objetos .
Jared Smith
@JaredSmith, não foi afirmado que a pergunta era sobre todos os objetos. Não ficou nem claro se era sobre uma cópia profunda ou superficial (eu assumiria uma cópia superficial usual, mas a resposta aceita é sobre uma cópia profunda). Quanto às classes personalizadas, se elas são suas, você pode apenas respeitar esse tipo de convenção no __init__método deles . Então, eu pensei que este método pode ser bom o suficiente para certos fins. De qualquer forma, estarei interessado em comentários informativos sobre essa sugestão.
21418 Alexey
Considere o class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argbásico como ele recebe. Se eu fizer foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>isso significa que sua copyfunção está quebrada até para as classes mais básicas. Novamente, é um truque legal (portanto, não há DV), mas não é uma resposta.
Jared Smith
@JaredSmith, vi que existe um copy.copymétodo para fazer cópias rasas, mas, talvez ingênuo, parece-me que deve ser da responsabilidade da classe fornecer um "construtor de cópias rasas". Nesse caso, por que não fornecer o mesmo kinf de interface que ele faz dicte listfaz? Portanto, se sua classe deseja assumir a responsabilidade de copiar seus objetos, por que não adicionar uma if isinstance(arg, type(self))cláusula __init__?
21418 Alexey
1
Porque você nem sempre tem controle sobre as classes que usa da maneira que define. Eles podem, apenas como um exemplo, ser programas C que possuem ligações Python (por exemplo, GTK, openalpr, partes do núcleo). Sem mencionar que, mesmo que você tenha pegado uma biblioteca de terceiros e adicionado métodos de cópia a todas as classes, como vai inseri-lo no gerenciamento de dependências?
Jared Smith