Wie kann ich eine Kopie eines Objekts in Python erstellen?

198

Ich möchte eine Kopie eines Objekts erstellen. Ich möchte, dass das neue Objekt alle Eigenschaften des alten Objekts (Werte der Felder) besitzt. Aber ich möchte unabhängige Objekte haben. Wenn ich also die Werte der Felder des neuen Objekts ändere, sollte das alte Objekt davon nicht betroffen sein.

römisch
quelle

Antworten:

179

Um eine vollständig unabhängige Kopie eines Objekts zu erhalten, können Sie die copy.deepcopy()Funktion verwenden.

Weitere Informationen zum flachen und tiefen Kopieren finden Sie in den anderen Antworten auf diese Frage und in der netten Erklärung in dieser Antwort auf eine verwandte Frage .

Sven Marnach
quelle
2
Diese Antwort wurde als "Keine Antwort" gekennzeichnet, gelöscht und nicht gelöscht - Metadiskussion hier: meta.stackoverflow.com/questions/377844/…
Aaron Hall
@ AaronHall Danke, dass du mich informiert hast! Dies ist sicherlich nicht die beste Antwort, die ich geschrieben habe, aber ich stimme der Entscheidung zu, dass sie nicht gewaltsam gelöscht werden sollte. Ich werde es ein wenig auffrischen, aber da es bereits Antworten mit allen Details gibt (insbesondere mit Ihren), werde ich mich kurz fassen.
Sven Marnach
70

Wie kann ich eine Kopie eines Objekts in Python erstellen?

Wenn ich also die Werte der Felder des neuen Objekts ändere, sollte das alte Objekt davon nicht betroffen sein.

Du meinst dann ein veränderliches Objekt.

In Python 3 erhalten Listen eine copyMethode (in 2 würden Sie ein Slice verwenden, um eine Kopie zu erstellen):

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

Flache Kopien

Flache Kopien sind nur Kopien des äußersten Behälters.

list.copy ist eine flache Kopie:

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

Sie erhalten keine Kopie der Innenobjekte. Sie sind dasselbe Objekt. Wenn sie also mutiert sind, wird die Änderung in beiden Containern angezeigt.

Tiefe Kopien

Tiefe Kopien sind rekursive Kopien jedes inneren Objekts.

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

Änderungen werden nicht im Original, sondern nur in der Kopie berücksichtigt.

Unveränderliche Gegenstände

Unveränderliche Objekte müssen normalerweise nicht kopiert werden. Wenn Sie es versuchen, gibt Python Ihnen nur das ursprüngliche Objekt:

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

Tupel haben nicht einmal eine Kopiermethode, also versuchen wir es mit einem Slice:

>>> tuple_copy_attempt = a_tuple[:]

Aber wir sehen, es ist das gleiche Objekt:

>>> tuple_copy_attempt is a_tuple
True

Ähnliches gilt für Strings:

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

und für Frozensets, obwohl sie eine copyMethode haben:

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

Wann unveränderliche Objekte kopiert werden sollen

Unveränderliche Objekte sollten kopiert werden, wenn ein veränderbares inneres Objekt kopiert werden muss.

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

Wie wir sehen können, ändert sich das Original nicht , wenn das innere Objekt der Kopie mutiert ist .

Benutzerdefinierte Objekte

Benutzerdefinierte Objekte speichern Daten normalerweise in einem __dict__Attribut oder in __slots__(einer tupelartigen Speicherstruktur).

Um ein kopierbares Objekt zu erstellen, definieren Sie __copy__(für flache Kopien) und / oder __deepcopy__(für tiefe Kopien).

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

Beachten Sie, dass deepcopyein Memo-Wörterbuch von id(original)(oder Identitätsnummern) Kopien enthält. Um ein gutes Verhalten bei rekursiven Datenstrukturen zu erzielen, stellen Sie sicher, dass Sie noch keine Kopie erstellt haben, und geben Sie diese gegebenenfalls zurück.

Machen wir also ein Objekt:

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

Und copymacht eine flache Kopie:

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

Und deepcopyjetzt macht eine tiefe Kopie:

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

Flache Kopie mit 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]

Tiefes Kopieren mit 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]

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

Getestet auf Python 3.6.5.

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

Ich glaube, dass das Folgende mit vielen gut erzogenen, in Python klassifizierten Klassen funktionieren sollte:

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

(Natürlich spreche ich hier nicht von "tiefen Kopien", was eine andere Geschichte ist und was möglicherweise kein sehr klares Konzept ist - wie tief ist tief genug?)

Gemäß meinen Tests mit Python 3 wird für unveränderliche Objekte wie Tupel oder Zeichenfolgen dasselbe Objekt zurückgegeben (da keine flache Kopie eines unveränderlichen Objekts erstellt werden muss), für Listen oder Wörterbücher wird jedoch eine unabhängige flache Kopie erstellt .

Natürlich funktioniert diese Methode nur für Klassen, deren Konstruktoren sich entsprechend verhalten. Mögliche Anwendungsfälle: Erstellen einer flachen Kopie einer Standard-Python-Containerklasse.

Alexey
quelle
Das ist ordentlich und alles, beantwortet aber die Frage nicht, da Ihre Kopierfunktion für benutzerdefinierte Klassen fehlschlägt und die Frage sich auf Objekte bezieht .
Jared Smith
@JaredSmith, es wurde nicht angegeben, dass die Frage über alle Objekte war. Es war nicht einmal klar, ob es sich um eine tiefe oder flache Kopie handelte (ich würde eine übliche flache Kopie annehmen, aber die akzeptierte Antwort ist eine tiefe). Wenn Sie benutzerdefinierte Klassen haben, können Sie diese Art von Konvention in ihrer __init__Methode respektieren . Daher dachte ich, dass diese Methode für bestimmte Zwecke gut genug sein könnte. In jedem Fall bin ich an informativen Kommentaren zu diesem Vorschlag interessiert.
Alexey
Betrachten Sie class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argBasic so wie es nur geht. Wenn ich das tue Das foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>bedeutet, dass Ihre copyFunktion selbst für die grundlegendsten Klassen unterbrochen ist. Auch hier ist es ein ordentlicher Trick (daher kein DV), aber keine Antwort.
Jared Smith
@JaredSmith, ich habe gesehen, dass es eine copy.copyMethode zum Erstellen flacher Kopien gibt, aber vielleicht naiv scheint es mir, dass es in der Verantwortung der Klasse liegen sollte, einen "Konstruktor für flache Kopien" bereitzustellen. In einem solchen Fall, warum nicht die gleiche Schnittstelle zur Verfügung stellen wie dictund list? Wenn Ihre Klasse die Verantwortung für das Kopieren ihrer Objekte übernehmen möchte, warum nicht eine if isinstance(arg, type(self))Klausel hinzufügen __init__?
Alexey
1
Da Sie nicht immer die Kontrolle über die Klassen haben, verwenden Sie die von Ihnen definierten Klassen. Dies können beispielsweise C-Programme mit Python-Bindungen sein (z. B. GTK, openalpr, Teile des Kerns). Ganz zu schweigen davon, dass Sie, selbst wenn Sie eine Bibliothek eines Drittanbieters verwendet und jeder Klasse Kopiermethoden hinzugefügt haben, diese in Ihr Abhängigkeitsmanagement einbinden können.
Jared Smith