Jak mogę utworzyć kopię obiektu w Pythonie?


199

Chciałbym stworzyć kopię obiektu. Chcę, aby nowy obiekt posiadał wszystkie właściwości starego obiektu (wartości pól). Ale chcę mieć niezależne obiekty. Jeśli więc zmienię wartości pól nowego obiektu, stary obiekt nie powinien mieć na to wpływu.

Odpowiedzi:


179

Aby uzyskać w pełni niezależną kopię obiektu, możesz użyć copy.deepcopy()funkcji.

Aby uzyskać więcej informacji na temat płytkiego i głębokiego kopiowania, zapoznaj się z innymi odpowiedziami na to pytanie oraz dobrym wyjaśnieniem w tej odpowiedzi na powiązane pytanie .


2
Ta odpowiedź została oznaczona jako „Brak odpowiedzi”, usunięta i cofnięta - meta dyskusja tutaj: meta.stackoverflow.com/questions/377844/...
Aaron Hall

@AaronHall Dzięki za poinformowanie mnie! Z pewnością nie jest to najlepsza odpowiedź, którą napisałem, ale w pewnym sensie zgadzam się z decyzją, że nie należy jej wymuszać. Poprawię to trochę, ale skoro są już odpowiedzi ze wszystkimi szczegółami (zwłaszcza twoimi), będę krótko mówić.
Sven Marnach,

70

Jak mogę utworzyć kopię obiektu w Pythonie?

Jeśli więc zmienię wartości pól nowego obiektu, stary obiekt nie powinien mieć na to wpływu.

Masz na myśli obiekt zmienny.

W Pythonie 3 listy uzyskują copymetodę (w 2 użyłbyś plasterka, aby wykonać kopię):

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

Płytkie kopie

Płytkie kopie to tylko kopie najbardziej zewnętrznego pojemnika.

list.copy jest płytką kopią:

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

Nie dostajesz kopii wewnętrznych obiektów. Są tym samym obiektem - więc po zmutowaniu zmiana pojawia się w obu kontenerach.

Głębokie kopie

Głębokie kopie są rekurencyjnymi kopiami każdego obiektu wewnętrznego.

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

Zmiany nie są odzwierciedlane w oryginale, tylko w kopii.

Niezmienne przedmioty

Niezmienne obiekty zwykle nie muszą być kopiowane. W rzeczywistości, jeśli spróbujesz, Python da ci oryginalny obiekt:

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

Krotki nawet nie mają metody kopiowania, więc wypróbujmy to z kawałkiem:

>>> tuple_copy_attempt = a_tuple[:]

Ale widzimy, że to ten sam obiekt:

>>> tuple_copy_attempt is a_tuple
True

Podobnie dla ciągów:

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

i dla frozensetów, mimo że mają copymetodę:

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

Kiedy kopiować niezmienne obiekty

Niezmienne obiekty powinny zostać skopiowane, jeśli chcesz skopiować zmienny obiekt wewnętrzny.

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

Jak widać, gdy obiekt wewnętrzny kopii jest zmutowany, oryginał się nie zmienia.

Obiekty niestandardowe

Obiekty niestandardowe zwykle przechowują dane w __dict__atrybucie lub w __slots__(kruchej strukturze pamięci).

Aby utworzyć obiekt do kopiowania, zdefiniuj __copy__(dla płytkich kopii) i / lub __deepcopy__(dla głębokich kopii).

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

Zauważ, że deepcopyprzechowuje słownik notatek id(original)(lub numery identyfikacyjne) do kopii. Aby cieszyć się dobrym zachowaniem z rekurencyjnymi strukturami danych, upewnij się, że nie masz jeszcze kopii, a jeśli tak, zwróć ją.

Zróbmy więc obiekt:

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

I copywykonuje płytką kopię:

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

A deepcopyteraz robi głęboką kopię:

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

10

Płytka kopia z 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]

Głęboka kopia z 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]

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

Testowane na Pythonie 3.6.5.


-2

Uważam, że następujące elementy powinny działać z wieloma dobrze zachowanymi klasami w Pythonie:

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

(Oczywiście nie mówię tu o „głębokich kopiach”, które są inną historią i które mogą nie być zbyt jasnym pojęciem - jak głębokie są wystarczająco głębokie?)

Według moich testów w Pythonie 3 dla niezmiennych obiektów, takich jak krotki lub łańcuchy, zwraca ten sam obiekt (ponieważ nie ma potrzeby wykonywania płytkiej kopii niezmiennego obiektu), ale dla list lub słowników tworzy niezależną płytką kopię .

Oczywiście ta metoda działa tylko w przypadku klas, których konstruktory zachowują się odpowiednio. Możliwe przypadki użycia: wykonanie płytkiej kopii standardowej klasy kontenera Python.


To jest w porządku, ale nie odpowiada na pytanie, ponieważ funkcja kopiowania nie działa dla klas niestandardowych, a pytanie dotyczyło obiektów .
Jared Smith

@JaredSmith, nie stwierdzono, że pytanie dotyczyło wszystkich obiektów. Nie było nawet jasne, czy chodzi o kopię głęboką czy płytką (zakładam, że jest to zwykła płytka wersja, ale przyjęta odpowiedź dotyczy głębokiej). Jeśli chodzi o klasy niestandardowe, jeśli są one twoje, możesz po prostu przestrzegać tego rodzaju konwencji w ich __init__metodzie. Pomyślałem więc, że ta metoda może być wystarczająca do pewnych celów. W każdym razie będę zainteresowany informacyjnymi komentarzami na temat tej sugestii.
Alexey

Zastanów się nad class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = argpodstawowymi. Jeśli tak, to foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>znaczy, że twoja copyfunkcja jest zepsuta dla nawet najbardziej podstawowych klas. Znów jest to fajna sztuczka (stąd brak DV), ale nie odpowiedź.
Jared Smith

@JaredSmith, widziałem, że istnieje copy.copymetoda robienia płytkich kopii, ale, być może, naiwnie, wydaje mi się, że zadaniem klasy powinno być zapewnienie „konstruktora płytkich kopii”. W takim przypadku dlaczego nie zapewnić tego samego kinf interfejsu dla niego dicti listzrobić? Jeśli więc twoja klasa chce wziąć odpowiedzialność za kopiowanie swoich obiektów, dlaczego nie dodać if isinstance(arg, type(self))klauzuli __init__?
Alexey

1
Ponieważ nie zawsze masz kontrolę nad klasami, których używasz tak, jak robisz te, które definiujesz. Mogą być, podobnie jak jeden przykład, programy C, które mają powiązania Pythona (np. GTK, openalpr, części rdzenia). Nie wspominając już o tym, że nawet jeśli weźmiesz bibliotekę strony trzeciej i dodasz metody kopiowania do każdej klasy, jak zamierzasz wplecić to w zarządzanie zależnościami?
Jared Smith
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.