Come posso creare una copia di un oggetto in Python?


199

Vorrei creare una copia di un oggetto. Voglio che il nuovo oggetto possieda tutte le proprietà del vecchio oggetto (valori dei campi). Ma voglio avere oggetti indipendenti. Quindi, se cambio i valori dei campi del nuovo oggetto, l'oggetto vecchio non dovrebbe esserne influenzato.

Risposte:


179

Per ottenere una copia completamente indipendente di un oggetto è possibile utilizzare la copy.deepcopy()funzione.

Per maggiori dettagli sulla copia superficiale e profonda, fare riferimento alle altre risposte a questa domanda e alla bella spiegazione in questa risposta a una domanda correlata .


2
Questa risposta è stata contrassegnata come "Non una risposta", cancellata e non cancellata - meta discussione qui: meta.stackoverflow.com/questions/377844/…
Aaron Hall

@AaronHall Grazie per avermelo fatto notare! Questa non è certamente la risposta migliore che ho scritto, ma sono in qualche modo d'accordo con la decisione che non dovrebbe essere forzatamente cancellata. Lo spazzolino un po ', ma dal momento che ci sono già risposte con tutti i dettagli (in particolare i tuoi), lo terrò breve.
Sven Marnach,

70

Come posso creare una copia di un oggetto in Python?

Quindi, se cambio i valori dei campi del nuovo oggetto, l'oggetto vecchio non dovrebbe esserne influenzato.

Intendi un oggetto mutabile allora.

In Python 3, gli elenchi ottengono un copymetodo (in 2, useresti una sezione per fare 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

Copie superficiali

Le copie superficiali sono solo copie del contenitore più esterno.

list.copy è una copia superficiale:

>>> 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'}}]

Non ottieni una copia degli oggetti interni. Sono lo stesso oggetto - quindi quando sono mutati, il cambiamento si presenta in entrambi i contenitori.

Copie profonde

Le copie profonde sono copie ricorsive di ciascun oggetto interno.

>>> 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'}}]

Le modifiche non si riflettono nell'originale, ma solo nella copia.

Oggetti immutabili

Gli oggetti immutabili di solito non devono essere copiati. In effetti, se ci provi, Python ti darà semplicemente l'oggetto originale:

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

Le tuple non hanno nemmeno un metodo di copia, quindi proviamo con una sezione:

>>> tuple_copy_attempt = a_tuple[:]

Ma vediamo che è lo stesso oggetto:

>>> tuple_copy_attempt is a_tuple
True

Allo stesso modo per le stringhe:

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

e per frozenset, anche se hanno un copymetodo:

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

Quando copiare oggetti immutabili

Gli oggetti immutabili devono essere copiati se è necessario copiare un oggetto interno mutabile.

>>> 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'],)

Come possiamo vedere, quando l'oggetto interno della copia è mutato, l'originale non cambia.

Oggetti personalizzati

Gli oggetti personalizzati di solito memorizzano i dati in un __dict__attributo o in __slots__(una struttura di memoria simile a una tupla).

Per creare un oggetto copiabile, definire __copy__(per copie superficiali) e / o __deepcopy__(per copie profonde).

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

Si noti che deepcopymantiene un dizionario di memoization di id(original)(o numeri di identità) alle copie. Per godere del buon comportamento con le strutture di dati ricorsive, assicurati di non averne già fatto una copia e, se lo hai, restituiscilo.

Quindi facciamo un oggetto:

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

E copyfa una copia superficiale:

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

E deepcopyora fa una copia profonda:

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

10

Copia superficiale 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 profonda 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]

Documentazione: https://docs.python.org/3/library/copy.html

Testato su Python 3.6.5.


-2

Credo che quanto segue dovrebbe funzionare con molti ben educati classificati in Python:

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

(Certo, non sto parlando di "copie profonde", che è una storia diversa e che potrebbe non essere un concetto molto chiaro: quanto è profonda abbastanza?)

Secondo i miei test con Python 3, per oggetti immutabili, come tuple o stringhe, restituisce lo stesso oggetto (perché non è necessario creare una copia superficiale di un oggetto immutabile), ma per elenchi o dizionari crea una copia superficiale indipendente .

Naturalmente questo metodo funziona solo per le classi i cui costruttori si comportano di conseguenza. Casi d'uso possibili: creazione di una copia superficiale di una classe contenitore Python standard.


Questo è pulito, ma non risponde alla domanda poiché la funzione di copia non riesce per le classi personalizzate e la domanda riguardava gli oggetti .
Jared Smith,

@JaredSmith, non è stato affermato che la domanda riguardasse tutti gli oggetti. Non era nemmeno chiaro se si trattasse di una copia profonda o superficiale (suppongo che sia normale una copia superficiale, ma la risposta accettata riguarda una copia profonda). Per quanto riguarda le classi personalizzate, se sono tue, puoi semplicemente rispettare questo tipo di convenzione nel loro __init__metodo. Quindi, ho pensato che questo metodo potrebbe essere abbastanza buono per determinati scopi. In ogni caso, sarò interessato a commenti informativi su questo suggerimento.
Alexey,

Considera class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argBasic come si arriva. Se lo faccio foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>significa che la tua copyfunzione è interrotta anche per le classi più elementari. Ancora una volta, è un trucco accurato (quindi niente DV), ma non una risposta.
Jared Smith,

@JaredSmith, ho visto che esiste un copy.copymetodo per fare copie superficiali, ma, forse ingenuamente, mi sembra che dovrebbe essere responsabilità della classe fornire un "costruttore di copie superficiali". In tal caso, perché non fornire lo stesso tipo di interfaccia dicte listfare? Quindi, se la tua classe vuole assumersi la responsabilità di copiare i suoi oggetti, perché non aggiungere una if isinstance(arg, type(self))clausola __init__?
Alexey,

1
Perché non hai sempre il controllo sulle classi che usi nel modo in cui le definisci. Possono essere, ad esempio, programmi C che hanno collegamenti Python (ad es. GTK, openalpr, parti del core). Per non parlare del fatto che anche se prendessi una libreria di terze parti e aggiungessi metodi di copia a ogni classe, come intendi inserirla nella tua gestione delle dipendenze?
Jared Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.