Odpowiedzi:
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 .
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ą copy
metodę (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 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 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 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ą copy
metodę:
>>> a_frozenset = frozenset('abc')
>>> frozenset_copy_attempt = a_frozenset.copy()
>>> frozenset_copy_attempt is a_frozenset
True
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 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 deepcopy
przechowuje 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 copy
wykonuje płytką kopię:
>>> c2 = copy(c1)
>>> c1 is c2
False
>>> c2.b.append(3)
>>> c1.b
[2, 3]
A deepcopy
teraz robi głęboką kopię:
>>> c3 = deepcopy(c1)
>>> c3.b.append(4)
>>> c1.b
[2, 3]
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.
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.
__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.
class Foo(object): def __init__(self, arg): super(Foo, self).__init__() self.arg = arg
podstawowymi. Jeśli tak, to foo = Foo(3) bar = copy(foo) print(foo.arg) # 3 print(bar.arg) # <__main__.Foo object at ...>
znaczy, że twoja copy
funkcja jest zepsuta dla nawet najbardziej podstawowych klas. Znów jest to fajna sztuczka (stąd brak DV), ale nie odpowiedź.
copy.copy
metoda 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 dict
i list
zrobić? Jeśli więc twoja klasa chce wziąć odpowiedzialność za kopiowanie swoich obiektów, dlaczego nie dodać if isinstance(arg, type(self))
klauzuli __init__
?