2017-02-21 22 views
7

Obiekt mojej klasy ma listę jako atrybut. Oznacza to, żeJeśli chcę nierekurencyjnej głębokiej kopii mojego obiektu, czy powinienem przesłonić kopię lub deepcopy w Pythonie?

class T(object): 
    def __init__(self, x, y): 
     self.arr = [x, y] 

Gdy obiekt jest kopiowany, chcę oddzielną listę arr, ale płytkie kopię zawartości listy (np x i y). Dlatego decyduję się na wdrożenie własnej metody kopiowania, która spowoduje odtworzenie listy, ale nie elementów w niej zawartych. Ale czy powinienem to nazwać __copy__() lub __deepcopy__()? Który z nich jest właściwą nazwą dla tego, co robię, zgodnie z semantyką Python?

Przypuszczam, że jest to __copy__(). Jeśli zadzwonię pod numer deepcopy(), spodziewałbym się, że klon będzie całkowicie oddzielony od oryginału. Jednak documentation mówi:

głęboka kopia konstruuje nowy obiekt związek, a następnie rekurencyjnie wkładki kopie do niego obiektów znajdujących się w oryginale.

Jest to mylące, mówiąc „wstawia kopie do niego” zamiast „wkładki głębokich kopii do niego”, zwłaszcza z naciskiem.

+0

Pojęcie "głęboki" pochodzi z części rekurencyjnej, w przeciwnym razie mówiąc "głęboka kopia tworzy nowy obiekt złożony, a następnie wstawia do niego głębokie kopie" będzie opisem rekurencyjnym. –

Odpowiedz

5

Prawidłowa metoda magiczna, którą możesz zastosować tutaj, to __copy__.

Zachowanie opisałeś jest głębsza niż domyślne zachowanie kopii zrobi (czyli dla obiektu, który nie przeszkadza w celu realizacji __copy__), ale nie jest to na tyle głęboko, aby nazwać deepcopy. Dlatego należy wdrożyć __copy__, aby uzyskać pożądane zachowanie.

Nie popełnia błąd myśląc, że po prostu przypisując inną nazwę broni „Kopiuj”:

t1 = T('google.com', 123) 
t2 = t1 # this does *not* use __copy__ 

To właśnie wiąże inną nazwę do tej samej instancji. Raczej metoda __copy__ jest zahaczony przez funkcję:

import copy 
t2 = copy.copy(t1) 
+0

Dzięki! Myślałem tak samo, ale czułem się niepewnie, gdy czytałem dokumentację Pythona. Dlaczego Python nie uczyni 'copy' wbudowanym jak' str' i 'repr'? – user31039

+0

Ponieważ nie ma zbyt wielu przypadków użycia. Twórcy Pythona na ogół pracują w stylu, który unika skutków ubocznych, tak więc nie obchodzi ich, gdzie w pamięci znajduje się obiekt. – wim

+0

@ user31039 Ponieważ większość wbudowanych można łatwo skopiować. Na przykład instancja może zostać przekazana do konstruktora ('dict ({'a': 1})' tworzy kopię) lub mieć jawną metodę 'copy':' {1,2,3} .copy() 'lub mieć nawet składnię, by utworzyć kopię: '[1,2,3] [:]'. W przypadku normalnych programów Pythona za pomocą wbudowanych po prostu nie potrzebujesz modułu kopiowania. Reklama: Dotknąłem także niektórych z tych punktów w mojej odpowiedzi. :) – MSeifert

1

myślę nadpisywania __copy__ jest dobrym pomysłem, jak wyjaśniono przez innych odpowiedzi z bardzo szczegółowo. Alternatywnym rozwiązaniem może być napisanie wyraźny sposób kopiowania być absolutnie jasne, na co nazywa się:

class T(object): 
    def __init__(self, x, y): 
     self.arr = [x, y] 

    def copy(self): 
     return T(*self.arr) 

Kiedy każdy przyszły czytelnik widzi t.copy() potem od razu wie, że metoda zwyczaj kopia została wdrożona. Wadą jest oczywiście to, że może nie działać dobrze z bibliotekami firm trzecich, które używają copy.copy(t).

+0

, więc twoja odpowiedź na pytanie "powinienem przesłonić kopię lub deepcopy?" z tytułu jest .... ani nie zapominajcie o 'copy.copy' razem? –

+0

To tylko alternatywny pomysł. Myślę, że ma pewne zalety, ale jestem również całkowicie świadomy negatywnych konsekwencji. –

+2

ok, z wyjątkiem tego, że jeśli zdefiniowano "__copy__", jest wywoływane, gdy wykonujesz 'copy.copy (t)'. Wydaje mi się, że tak naprawdę nie oferujesz rozwiązania zadawanego pytania. (Zakładam, że z tego powodu był downvoter) –

3

To zależy od pożądanego zachowania klasy, które bierze pod uwagę decyzję o nadpisaniu (__copy__ lub __deepcopy__).

W ogólnym copy.deepcopy działa głównie poprawnie, to po prostu kopiuje wszystko (recursivly), więc wystarczy tylko zastąpić go, jeśli istnieje jakiś atrybut, który nie mogą być kopiowane (nigdy!).

Z drugiej strony należy zdefiniować __copy__ tylko wtedy, gdy użytkownicy (w tym ty) nie będą oczekiwać zmian, które będą propagować do skopiowanych instancji. Na przykład, jeśli po prostu owiniesz zmienny typ (np. list) lub użyjesz typów zmiennych jako szczegółów implementacji.

Istnieje również przypadek, że minimalny zestaw atrybutów do skopiowania nie jest jasno zdefiniowany. W takim przypadku zastąpiłbym także __copy__, ale może podniosę tam TypeError i możliwe jest włączenie jednej (lub kilku) dedykowanych publicznych metod copy.

Jednak moim zdaniem arr liczy się jako szczegółów implementacji i dlatego chciałbym zastąpić __copy__:

class T(object): 
    def __init__(self, x, y): 
     self.arr = [x, y] 

    def __copy__(self): 
     new = self.__class__(*self.arr) 
     # ... maybe other stuff 
     return new 

Wystarczy, aby pokazać, że działa zgodnie z oczekiwaniami:

from copy import copy, deepcopy 

x = T([2], [3]) 
y = copy(x) 
x.arr is y.arr  # False 
x.arr[0] is y.arr[0] # True 
x.arr[1] is y.arr[1] # True 

x = T([2], [3]) 
y = deepcopy(x) 
x.arr is y.arr  # False 
x.arr[0] is y.arr[0] # False 
x.arr[1] is y.arr[1] # False 

Wystarczy krótka notatka O oczekiwaniach:

Użytkownicy generalnie oczekują hat możesz przekazać instancję do konstruktora, aby utworzyć kopię minimalną (podobną lub identyczną do __copy__). Na przykład:

lst1 = [1,2,3,4] 
lst2 = list(lst1) 
lst1 is lst2  # False 

niektórych typów Pythona mają wyraźne copy sposób, że (jeśli występuje) powinna zrobić to samo jak __copy__. Które pozwoliłyby jednoznacznie przekazać w parametrach (choć nie widziałem w akcji jeszcze):

lst3 = lst1.copy() # python 3.x only (probably) 
lst3 is lst1  # False 

Jeśli klasa powinna być używana przez innych prawdopodobnie trzeba rozważyć te kwestie, jednak jeśli chcesz tylko do spraw, aby twoja klasa działała z copy.copy, a następnie po prostu nadpisaj __copy__.

Powiązane problemy