2009-09-30 25 views
60

Więc po prostu ustalić, czuję, rozumiem różnicę między copy vs. deepcopy w module kopiowania i Użyłem copy.copy i copy.deepcopy przed sukcesem, ale jest to pierwszy raz, kiedy rzeczywiście poszedł o przeciążeniu __copy__ i __deepcopy__ metody. Przeglądałem już i przeglądałem wbudowane moduły Pythona, aby wyszukać wystąpienia funkcji __copy__ i __deepcopy__ (np. sets.py, decimal.py i fractions.py), ale nadal nie jestem w 100% pewny, że mam to dobrze .Jaki jest właściwy sposób na przesłonięcie operacji kopiowania/kopiowania na obiekcie w Pythonie?

Oto mój scenariusz:

Mam obiekt konfiguracji, która głównie składa się tylko z prostych właściwości (choć będzie to potencjalnie mieć list innych niż prymitywnych obiektów w nim). Początkowo zamierzam utworzyć instancję jednego obiektu konfiguracyjnego z domyślnym zestawem wartości. Ta konfiguracja zostanie przekazana do wielu innych obiektów (aby zapewnić, że wszystkie obiekty zaczynają się od tej samej konfiguracji). Jednak gdy rozpocznie się interakcja użytkownika, każdy obiekt będzie musiał móc dostosować konfiguracje niezależnie, nie wpływając na konfiguracje drugiej strony (co oznacza dla mnie, że muszę wprowadzić deepcopys z mojej początkowej konfiguracji do przekazania).

Oto przykładowy obiekt:

class ChartConfig(object): 

    def __init__(self): 

     #Drawing properties (Booleans/strings) 
     self.antialiased = None 
     self.plot_style = None 
     self.plot_title = None 
     self.autoscale = None 

     #X axis properties (strings/ints) 
     self.xaxis_title = None 
     self.xaxis_tick_rotation = None 
     self.xaxis_tick_align = None 

     #Y axis properties (strings/ints) 
     self.yaxis_title = None 
     self.yaxis_tick_rotation = None 
     self.yaxis_tick_align = None 

     #A list of non-primitive objects 
     self.trace_configs = [] 

    def __copy__(self): 
     pass 

    def __deepcopy__(self, memo): 
     pass 

Co jest właściwym sposobem realizacji copy i deepcopy metod na tym obiekcie w celu zapewnienia copy.copy i copy.deepcopy dać mi właściwego zachowania? Aktualnie używam Pythona 2.6.2.

Z góry dziękuję!

+0

Czy to działa? Czy są jakieś problemy? –

+0

Myślałem, że wciąż mam problemy z dzielonymi referencjami, ale jest to całkowicie możliwe, że zawiodłem gdzie indziej. Sprawdzę podwójnie na podstawie wpisu @ MortenSiebuhr, kiedy otrzymam szansę i zaktualizuję wyniki. –

+0

Z mojego obecnie ograniczonego rozumowania oczekiwałbym, że copy.deepcopy (ChartConfigInstance) zwróci nowe wystąpienie, które nie będzie miało żadnych wspólnych odniesień z oryginałem (bez ponownego wgłębiania samego siebie). Czy to jest nieprawidłowe? – emschorsch

Odpowiedz

52

Zalecenia dostosowywania są na samym końcu docs page:

Klasy mogą wykorzystywać te same interfejsy kopiowania sterowania, które wykorzystują oni do sterowania wytrawiania. Zobacz opis modułu pickle, aby uzyskać informacje na temat metod . Moduł kopiowania nie wymaga użycia modułu copy_reg rejestracji .

Aby klasie zdefiniować własną implementację kopiowaniem, można go określić metod specjalnych __copy__() i __deepcopy__(). Ten pierwszy jest wywoływany w celu wykonania operacji płytkiej kopiowania ; nie przekazano żadnych dodatkowych argumentów. Ten ostatni jest wywoływany pod numerem w celu wykonania operacji kopiowania głębokiego; to jest przekazywany jeden argument, słownik memo . Jeśli implementacja __deepcopy__() wymaga wykonania głębokiej kopii komponentu, powinna wywołać z funkcją deepcopy() ze składnikiem jako argumentem pierwszym, a jako drugi argument ze słownikiem memo .

Ponieważ nie wydaje się, aby dbać o trawiącej personalizacji, definiując __copy__ i __deepcopy__ pewnością wydaje się, że właściwa droga dla Ciebie.

szczególności __copy__ (płytkie kopia) jest bardzo łatwy w twoim przypadku ...:

def __copy__(self): 
    newone = type(self)() 
    newone.__dict__.update(self.__dict__) 
    return newone 

__deepcopy__ byłby podobny (przyjmując memo Arg też) ale przed powrotem musiałby zadzwonić self.foo = deepcopy(self.foo, memo) dla każdego atrybutu self.foo, który wymaga głębokiego kopiowania (w zasadzie atrybuty będące kontenerami - listami, dyktami, nieparzystymi obiektami, które przechowują inne rzeczy przez ich s).

+0

Myślę, że słyszałem, że lepiej jest przesłonić '__getstate__' /' __setstate__', aby zaimplementować kopiowanie. Czy jestem zdezorientowany? – u0b34a0f6ae

+1

@kizer, są w porządku, aby dostosować pikowanie/rozpakowywanie, a także kopiowanie, ale jeśli nie dbasz o wytrawianie, prostsze i bardziej bezpośrednie jest użycie '__copy__' /' __deepcopy__'. –

+2

To nie wydaje się być bezpośrednim tłumaczeniem kopii/deepcopy. Ani copy, ani deepcopy nie wywołują konstruktora kopiowanego obiektu. Rozważ ten przykład. klasy Test1 (object): def __init __ (self): druku "% s% s" % (self .__ class __.__ name__ "__init__") klasa Test2 (Test1): def __copy __ (self): nowy typ = (self)() powrócić nowy t1 = Test1() copy.copy (t1) t2 = Test2() copy.copy (t2) –

5

Być może nie podoba mi się szczegół, ale tutaj jest;

Od copy docs;

  • Płytka kopii tworzy nowy obiekt, a następnie związek (w możliwym zakresie) jest wprowadzany do niego odniesienie do przedmiotów znajdujących się w oryginale.
  • Głęboka kopia tworzy nowy obiekt złożony, a następnie rekurencyjnie wstawia do niego kopie obiektów znalezionych w oryginale.

Innymi słowy: copy() skopiuje tylko górny element i resztę pozostawić jako wskaźniki do oryginalnej konstrukcji. deepcopy() będzie rekurencyjnie kopiować wszystko.

To znaczy, że deepcopy() jest tym, czego potrzebujesz.

Jeśli potrzebujesz czegoś konkretnego, możesz zastąpić __copy__() lub __deepcopy__(), zgodnie z opisem w instrukcji. Osobiście prawdopodobnie zastosowałbym prostą funkcję (na przykład config.copy_config() lub podobną), aby jasno stwierdzić, że nie jest to standardowe zachowanie Pythona.

+2

* Aby klasa mogła zdefiniować własną implementację kopii, może zdefiniować specjalne metody '__copy __ (') i '__deepcopy __()'. * Http://docs.python.org/library/copy.html – SilentGhost

+0

I ' Dokładnie sprawdzę mój kod, dzięki. Czuję się głupio, jeśli to był prosty błąd w innym miejscu :-P –

+0

@MortenSiebuhr Masz rację. Nie do końca wiem, że kopiowanie/deepcopy może zrobić cokolwiek domyślnie, bez przesłonięcia tych funkcji. Szukałem prawdziwego kodu, ale mogę go poprawić później (np. Jeśli nie chcę kopiować wszystkich atrybutów), więc dałem wam prawo głosu, ale mam zamiar odpowiedzieć z @ AlexMartinellim. Dzięki! –

52

Zestawiając odpowiedź Alex Martelli i komentarz Roba Younga masz następujący kod:

from copy import copy, deepcopy 

class A(object): 
    def __init__(self): 
     print 'init' 
     self.v = 10 
     self.z = [2,3,4] 

    def __copy__(self): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     result.__dict__.update(self.__dict__) 
     return result 

    def __deepcopy__(self, memo): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     memo[id(self)] = result 
     for k, v in self.__dict__.items(): 
      setattr(result, k, deepcopy(v, memo)) 
     return result 

a = A() 
a.v = 11 
b1, b2 = copy(a), deepcopy(a) 
a.v = 12 
a.z.append(5) 
print b1.v, b1.z 
print b2.v, b2.z 

wydruki

init 
11 [2, 3, 4, 5] 
11 [2, 3, 4] 

tutaj __deepcopy__ wypełnia memo dict aby uniknąć nadmiaru kopiowanie w przypadku samego obiektu jest przywoływany od swojego członka.

+2

dzięki za odpowiedź, doktorzy naprawdę brakuje w tym punkcie! czy istnieje powód, dla którego "z kopii głębokiego importu" zarówno na górze pliku, jak i wewnątrz metody '__deepcopy__'? – Anentropic

+0

Otrzymuję błąd z powyższą implementacją 'klasa Transporter nie ma atrybutu '__new __'' (python 2.7). Próbuję przesłonić '__deepcopy__' – bytestorm

+0

@bestestorm, co to jest' Transporter'? –

4

Nie jest jasne, dlaczego musisz zastąpić te metody, ponieważ nie chcesz dostosowywać metod kopiowania.

Tak czy inaczej, jeśli chcemy, aby dostosować głęboką kopię (na przykład poprzez dzielenie niektórych atrybutów i kopiowanie innych), oto rozwiązanie:

from copy import deepcopy 


def deepcopy_with_sharing(obj, shared_attribute_names, memo=None): 
    ''' 
    Deepcopy an object, except for a given list of attributes, which should 
    be shared between the original object and its copy. 

    obj is some object 
    shared_attribute_names: A list of strings identifying the attributes that 
     should be shared between the original and its copy. 
    memo is the dictionary passed into __deepcopy__. Ignore this argument if 
     not calling from within __deepcopy__. 
    ''' 
    assert isinstance(shared_attribute_names, (list, tuple)) 
    shared_attributes = {k: getattr(obj, k) for k in shared_attribute_names} 

    if hasattr(obj, '__deepcopy__'): 
     # Do hack to prevent infinite recursion in call to deepcopy 
     deepcopy_method = obj.__deepcopy__ 
     obj.__deepcopy__ = None 

    for attr in shared_attribute_names: 
     del obj.__dict__[attr] 

    clone = deepcopy(obj) 

    for attr, val in shared_attributes.iteritems(): 
     setattr(obj, attr, val) 
     setattr(clone, attr, val) 

    if hasattr(obj, '__deepcopy__'): 
     # Undo hack 
     obj.__deepcopy__ = deepcopy_method 
     del clone.__deepcopy__ 

    return clone 



class A(object): 

    def __init__(self): 
     self.copy_me = [] 
     self.share_me = [] 

    def __deepcopy__(self, memo): 
     return deepcopy_with_sharing(self, shared_attribute_names = ['share_me'], memo=memo) 

a = A() 
b = deepcopy(a) 
assert a.copy_me is not b.copy_me 
assert a.share_me is b.share_me 

c = deepcopy(b) 
assert c.copy_me is not b.copy_me 
assert c.share_me is b.share_me 
+0

Czy klon nie potrzebuje też resetowania metody "__deepcopy__", ponieważ będzie miał "__deepcopy__" = Brak? – flutefreak7

+0

Nie. Jeśli nie zostanie znaleziona metoda '__deepcopy__' (lub' obj .__ deepcopy__' zwróci Brak), to 'deepcopy' powróci do standardowej funkcji głębokiego kopiowania. Można to zobaczyć [tutaj] (https://github.com/python/cpython/blob/3.6/Lib/copy.py#L159) – Peter

+0

Ale czy b nie będzie miał możliwości pogłębiania z udostępnianiem? c = deepcopy (a) różni się od d = deepcopy (b), ponieważ d byłoby domyślnym deepcopy, gdzie c miałby niektóre wspólne attrs z a. – flutefreak7

4

Po Peter's excellent answer, w celu realizacji niestandardowego deepcopy, z minimalną zmianą do domyślnej implementacji (nptylko modyfikując pole jakbym potrzebne):

class Foo(object): 
    def __deepcopy__(self, memo): 
     deepcopy_method = self.__deepcopy__ 
     self.__deepcopy__ = None 
     cp = deepcopy(self, memo) 
     self.__deepcopy__ = deepcopy_method 

     # custom treatments 
     # for instance: cp.id = None 

     return cp 
0

Opierając się na czystej odpowiedź Antony Hatchkins', oto moja wersja, gdzie klasa w pytaniu wynika z innej klasy niestandardowego (ST musimy wywołać super):

class Foo(FooBase): 
    def __init__(self, param1, param2): 
     self._base_params = [param1, param2] 
     super(Foo, result).__init__(*self._base_params) 

    def __copy__(self): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     result.__dict__.update(self.__dict__) 
     super(Foo, result).__init__(*self._base_params) 
     return result 

    def __deepcopy__(self, memo): 
     cls = self.__class__ 
     result = cls.__new__(cls) 
     memo[id(self)] = result 
     for k, v in self.__dict__.items(): 
      setattr(result, k, copy.deepcopy(v, memo)) 
     super(Foo, result).__init__(*self._base_params) 
     return result 
Powiązane problemy