Bir Python nesnesi için kopyalama / derin kopya işlemlerini nasıl geçersiz kılınır?


104

Ben arasındaki farkı anlamak copyvs. deepcopykopya modülünde. Daha önce copy.copyve copy.deepcopybaşarıyla kullandım , ancak ilk kez gerçekten __copy__ve __deepcopy__yöntemlerini aşırı yüklemeye başladım . Zaten yoluyla etrafına Googled ve baktım örnekleri aramaya Python modülleri dahili __copy__ve __deepcopy__fonksiyonları (örn sets.py, decimal.pyve fractions.py), ama yine de% 100 emin ben doğru var benim.

İşte benim senaryom:

Bir yapılandırma nesnem var. Başlangıçta, bir yapılandırma nesnesini varsayılan bir değerler kümesiyle başlatacağım. Bu konfigürasyon, diğer birçok nesneye devredilecektir (tüm nesnelerin aynı konfigürasyonla başlamasını sağlamak için). Bununla birlikte, kullanıcı etkileşimi başladığında, her nesnenin, birbirlerinin konfigürasyonlarını etkilemeden kendi konfigürasyonlarını bağımsız olarak ayarlaması gerekir (bu bana, başkalarını teslim etmek için ilk konfigürasyonumun derin kopyalarını yapmam gerektiğini söylüyor).

İşte örnek bir nesne:

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 

Bana uygun davranışı sağlamak ve vermek için bu nesne üzerinde copyve deepcopyyöntemlerini uygulamanın doğru yolu nedir ?copy.copycopy.deepcopy


Çalışıyor mu? Sorunlar var mı?
Ned Batchelder

Paylaşılan referanslarla hala sorun yaşadığımı sanıyordum, ancak başka bir yerde batırmam tamamen mümkün. Bir şansım olduğunda ve sonuçları güncellediğimde @ MortenSiebuhr'un gönderisine göre iki kez kontrol edeceğim.
Brent Kod Yazıyor

Şu anda sınırlı olan anlayışımdan, copy.deepcopy'nin (ChartConfigInstance) orijinalle paylaşılmış referansları olmayan yeni bir örnek döndürmesini bekliyorum (derin kopyayı yeniden uygulamadan). Bu yanlış mı?
emschorsch

Yanıtlar:


83

Özelleştirme önerileri, dokümanlar sayfasının en sonundadır :

Sınıflar, asitlemeyi kontrol etmek için kullandıkları kopyalamayı kontrol etmek için aynı arayüzleri kullanabilir. Bu yöntemler hakkında bilgi için modül turşusu açıklamasına bakın. Kopyalama modülü copy_reg kayıt modülünü kullanmaz.

Bir sınıfın kendi kopya uygulamasını tanımlaması için özel yöntemler __copy__()ve __deepcopy__(). İlki, sığ kopyalama işlemini uygulamaya çağırılır; hiçbir ek argüman iletilmez. İkincisi, derin kopyalama işlemini gerçekleştirmek için çağrılır; bir argüman aktarılır, not sözlüğü. Eğer __deepcopy__() uygulama bir bileşenin derin bir kopyasını yapmak gerekiyor, bu aramalısınız deepcopy()birinci argüman ve ikinci argüman olarak not sözlük olarak bileşenle fonksiyonunu.

Asitleme özelleştirme ile ilgilenmiyor göründüğünüz için, tanımlama __copy__ve __deepcopy__kesinlikle sizin için doğru yol gibi görünüyor.

Özellikle, __copy__(sığ kopya) sizin durumunuzda oldukça kolaydır ...:

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

__deepcopy__benzer olacaktır (bir memoargümanı kabul etmek de) ancak dönüşten önce derin kopyalamaya ihtiyaç duyan self.foo = deepcopy(self.foo, memo)herhangi bir özniteliği self.foo(esasen kaplar olan öznitelikler - listeler, diktler, diğer şeyleri kendi listelerinde tutan ilkel olmayan nesneler) çağırması gerekir __dict__.


1
@kaizer, onlar ince / dekapaj unpickling yanı sıra kopyalama özelleştirmek için, ancak asitleme umurumda değil, eğer daha basit ve kullanımı daha doğrudan __copy__/ __deepcopy__.
Alex Martelli

4
Bu, kopya / derin kopyanın doğrudan çevirisi gibi görünmüyor. Ne copy ne de deepcopy, kopyalanan nesnenin yapıcısını çağırmaz. Bu örneği düşünün. class Test1 (nesne): def init __ (self): print "% s.% s"% (self .__ class .__ name__, " init ") class Test2 (Test1): def __copy __ (self): new = type (self) () yeni t1 = Test1 () copy.copy (t1) t2 = Test2 () copy.copy (t2)
Rob Young

12
Bence (self) () türü yerine, cls = self .__ class__; cls .__ new __ (cls) yapıcılar arayüzüne duyarsız olacaktır (özellikle alt sınıflandırma için). Ancak burada gerçekten önemli değil.
Juh_

11
Neden self.foo = deepcopy(self.foo, memo)...? Gerçekten demek istemedin newone.foo = ...mi?
Alois Mahdal

4
@ Juh_'nin yorumu yerinde. Aramak istemezsin __init__. Kopyanın yaptığı bu değil. Ayrıca, çoğu zaman asitleme ve kopyalamanın farklı olması gereken bir kullanım durumu vardır. Aslında, kopyanın neden varsayılan olarak asitleme protokolünü kullanmaya çalıştığını bile bilmiyorum. Kopyalama, bellek içi manipülasyon içindir; asitle temizleme, çağlar arası kalıcılık içindir; birbirleriyle çok az ilişkisi olan tamamen farklı şeylerdir.
Nimrod

102

Alex Martelli'nin cevabı ile Rob Young'un yorumunu bir araya getirerek aşağıdaki kodu elde edersiniz:

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

baskılar

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

burada , nesnenin üyesinden referans alınması durumunda fazla kopyalamadan kaçınmak __deepcopy__için memodikte doldurulur .


2
@bytestorm nedir Transporter?
Antony Hatchkins

@AntonyHatchkins Transporter, yazdığım sınıfımın adı. Bu sınıf için, derin kopya davranışını geçersiz kılmak istiyorum.
2018

1
@bytestorm içeriği Transporternedir?
Antony Hatchkins

1
__deepcopy__Sonsuz yinelemeden kaçınmak için bir test içermesi gerektiğini düşünüyorum : <! - dil: lang-python -> d = id (öz) sonuç = memo.get (d, Yok) eğer sonuç Yok değilse: sonucu
döndür

@AntonyHatchkins Sonsuz özyinelemeyi önlemek için gerçekte nerede memo[id(self)] kullanıldığı, gönderinizden hemen anlaşılmıyor . Ben birlikte bir koyduk kısa bir örnek ortaya koymaktadır copy.deepcopy()onun eğer içten bir nesneye çağrı iptal eder id()bir anahtardır memodoğru? Bunu varsayılandeepcopy() olarak kendi başına yapıyor gibi göründüğünü de belirtmekte fayda var , bu da elle tanımlamanın gerçekten gerekli olduğu bir durumu hayal etmeyi zorlaştırıyor ...__deepcopy__
Jonathan H

15

Peter'ın mükemmel cevabının ardından , varsayılan uygulamada en az değişiklikle özel bir derin kopya uygulamak için (örneğin, sadece ihtiyacım olduğu gibi bir alanı değiştirmek):

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

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

        return cp

1
delattr(self, '__deepcopy__')o zaman kullanmak tercih edilir setattr(self, '__deepcopy__', deepcopy_method)mi?
joel

Bu cevaba göre her ikisi de eşdeğerdir; ancak setattr, adı dinamik olan / kodlama sırasında bilinmeyen bir öznitelik ayarlarken daha kullanışlıdır.
Eino Gourdin

1
Bu benim kişisel favorim ve bunu bir nesnenin bir kaydediciye sahip olduğu, daha sonra turşu yapılamayan bir iplik kilidine sahip olduğu üretimde kullanıyorum. Kaydediciyi kaydedin, ayarlayın None, diğer her şey için varsayılanı arayın ve sonra geri koyun. Geleceğe hazır çünkü bir alanı ele almayı unutmaktan endişelenmeme gerek yok ve miras alınan sınıflar "sadece çalışır".
Aaron D. Marasco

BTW delattr()Birini denedim ve Python2.7'de başarısız oldu AttributeError. "Ayarla None", kullandığım şey.
Aaron D. Marasco

9

Kopyalama yöntemlerinde herhangi bir özelleştirme yapmak istemediğinizden, neden bu yöntemleri geçersiz kılmanız gerektiğine dair sorununuz net değil.

Her neyse, derin kopyayı özelleştirmek istiyorsanız (örneğin, bazı öznitelikleri paylaşıp diğerlerini kopyalayarak), işte bir çözüm:

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

Klonun __deepcopy__, __deepcopy__= Yok olacağından, yöntemin sıfırlanmasına da ihtiyacı yok mu?
flutefreak7

2
Hayır! Eğer __deepcopy__yöntem bulundu (ya da olmasın obj.__deepcopy__döner Yok), sonra deepcopytekrar standart derin kopyalama işlevi düşer. Bu görülebilir burada
Peter

1
Ama o zaman b, paylaşarak derin kopyalama yeteneğine sahip olmayacak mı? c = deepcopy (a), d = deepcopy (b) 'den farklı olacaktır çünkü d, c'nin a ile bazı paylaşılan özniteliklere sahip olacağı varsayılan bir derin kopya olacaktır.
flutefreak7

1
Ah, şimdi ne dediğini anlıyorum. İyi bir nokta. Sahte __deepcopy__=Noneözelliği klondan silerek düzelttim sanırım . Yeni kodu görün.
Peter

1
python uzmanlarına açık olabilir: bu kodu python 3'te kullanıyorsanız, "for attr, val in shared_attributes.iteritems ():" with "for attr, val in shared_attributes.items ():"
complexM

6

Ayrıntılardan biraz uzak olabilirim ama işte burada;

Gönderen copydocs ;

  • Yüzeysel bir kopya yeni bir bileşik nesne oluşturur ve ardından (mümkün olduğu ölçüde) orijinalde bulunan nesnelere referanslar ekler.
  • Derin bir kopya yeni bir bileşik nesne oluşturur ve ardından orijinalde bulunan nesnelerin kopyalarını yinelemeli olarak ona ekler.

Başka bir deyişle: copy()yalnızca en üstteki öğeyi kopyalayacak ve geri kalanını orijinal yapıya işaretçiler olarak bırakacaktır. deepcopy()özyinelemeli olarak her şeyi kopyalar.

Yani deepcopy()ihtiyacın olan şey bu.

Gerçekten spesifik bir şey yapmanız gerekiyorsa , kılavuzda açıklandığı gibi __copy__()veya geçersiz kılabilirsiniz __deepcopy__(). Şahsen, config.copy_config()Python standart davranışı olmadığını açıklığa kavuşturmak için muhtemelen düz bir işlev (örneğin veya benzeri) uygulayabilirim.


3
Bir sınıfın kendi kopya uygulamasını tanımlaması için özel yöntemler tanımlayabilir __copy__() ve __deepcopy__(). docs.python.org/library/copy.html
SilentGhost

Kodumu iki kez kontrol edeceğim, teşekkürler. Bu başka bir yerde basit bir hata olsaydı kendimi aptal hissedeceğim :-P
Brent Kod Yazıyor

@MortenSiebuhr Doğru söylüyorsunuz. Bu işlevleri geçersiz kılmadan, kopya / derin kopyanın varsayılan olarak her şeyi yapacağından tam olarak emin değildim. Daha sonra ince ayar yapabileceğim halde gerçek kodu arıyordum (örn. Tüm nitelikleri kopyalamak istemiyorsam), bu yüzden size bir oy verdim ama @ AlexMartinelli'nin cevabını kullanacağım. Teşekkürler!
Brent

2

copyModül sonunda kullandığı __getstate__()/ dekapaj protokolü bunlar da geçersiz kılma için geçerli hedeflerdir, bu yüzden.__setstate__()

Varsayılan uygulama yalnızca __dict__sınıfın super()adını döndürür ve ayarlar , böylece yukarıda , Eino Gourdin'in akıllı numarası hakkında endişelenmenize gerek kalmaz .


1

Antony Hatchkins'in temiz cevabına dayanarak, işte söz konusu sınıfın başka bir özel sınıftan türetildiği sürümüm (st aramamız gerekir 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.