पायथन ऑब्जेक्ट के लिए कॉपी / डीपकोपी ऑपरेशन को ओवरराइड कैसे करें?


101

मैं copyबनाम deepcopyप्रति मॉड्यूल में अंतर को समझता हूं । मैंने सफलतापूर्वक उपयोग किया है copy.copyऔर copy.deepcopyपहले भी किया है , लेकिन यह पहली बार है जब मैं वास्तव में ओवरलोडिंग __copy__और __deepcopy__तरीकों के बारे में गया हूं । मैं पहले से ही चारों ओर Google पर और के माध्यम से देखा है में निर्मित अजगर मॉड्यूल के उदाहरण देखने के लिए __copy__और __deepcopy__कार्यों (उदाहरण के लिए sets.py, decimal.pyऔर fractions.py), लेकिन मैं अभी भी नहीं 100% यकीन है कि मैं यह सही मिल गया है।

यहाँ मेरा परिदृश्य है:

मेरे पास एक कॉन्फ़िगरेशन ऑब्जेक्ट है। प्रारंभ में, मैं मानों के डिफ़ॉल्ट सेट के साथ एक कॉन्फ़िगरेशन ऑब्जेक्ट को इंस्टेंट करने जा रहा हूं। यह कॉन्फ़िगरेशन कई अन्य ऑब्जेक्ट्स को सौंपा जाएगा (यह सुनिश्चित करने के लिए कि सभी ऑब्जेक्ट एक ही कॉन्फ़िगरेशन से शुरू हों)। हालांकि, एक बार उपयोगकर्ता के बातचीत शुरू होने के बाद, प्रत्येक ऑब्जेक्ट को एक-दूसरे के कॉन्फ़िगरेशन को प्रभावित किए बिना स्वतंत्र रूप से इसके कॉन्फ़िगरेशन को ट्विस्ट करने की आवश्यकता होती है (जो मुझे कहता है कि मुझे अपने प्रारंभिक कॉन्फ़िगरेशन के डीपकोप बनाने के लिए चारों ओर हाथ करने की आवश्यकता होगी)।

यहाँ एक नमूना वस्तु है:

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 

लागू करने के लिए सही तरीका क्या है copyऔर deepcopyयह सुनिश्चित करने के लिए इस वस्तु पर तरीकों copy.copyऔर copy.deepcopyमुझे उचित व्यवहार दे सकता है?


क्या यह काम करता है? क्या समस्याएं हैं?
नेड बाचेल्डर

मुझे लगा कि मुझे अभी भी साझा संदर्भों के साथ समस्या हो रही है, लेकिन यह पूरी तरह से संभव है कि मैंने कहीं और गड़बड़ कर दी। जब भी मुझे मौका मिलेगा और परिणामों के साथ अपडेट करूंगा, मैं @ MortenSiebuhr के पोस्ट के आधार पर दोबारा जांच करूंगा।
ब्रेंट राइट्स कोड

मेरी वर्तमान में सीमित समझ से मैं copy.deepcopy (ChartConfigInstance) से एक नया उदाहरण लौटाने की उम्मीद करूंगा, जिसमें मूल के साथ कोई भी साझा संदर्भ नहीं होगा (डीपकोपी को फिर से लागू किए बिना)। क्या यह गलत है?
Emschorsch

जवाबों:


82

कस्टमाइज़ करने की सिफारिशें डॉक्स पृष्ठ के बहुत अंत में हैं :

क्लासेस नकल करने को नियंत्रित करने के लिए समान इंटरफेस का उपयोग कर सकते हैं जो वे अचार को नियंत्रित करने के लिए उपयोग करते हैं। इन तरीकों की जानकारी के लिए मॉड्यूल अचार का वर्णन देखें। प्रतिलिपि मॉड्यूल copy_reg पंजीकरण मॉड्यूल का उपयोग नहीं करता है।

अपनी खुद की कॉपी कार्यान्वयन को परिभाषित करने के लिए एक वर्ग के लिए, यह विशेष विधियों __copy__()और परिभाषित कर सकता है __deepcopy__()। पूर्व को उथले कॉपी ऑपरेशन को लागू करने के लिए कहा जाता है; कोई अतिरिक्त तर्क पारित नहीं किया जाता है। उत्तरार्द्ध को गहरी प्रतिलिपि संचालन को लागू करने के लिए कहा जाता है; यह एक तर्क पारित किया जाता है, ज्ञापन शब्दकोश। यदि __deepcopy__() कार्यान्वयन को किसी घटक की गहरी प्रतिलिपि बनाने की आवश्यकता होती है, तो इसे deepcopy()फ़ंक्शन को पहले तर्क के रूप में और दूसरे तर्क के रूप में ज्ञापन शब्दकोश में कॉल करना चाहिए ।

चूंकि आप अचार अनुकूलन के बारे में परवाह नहीं करते हैं, इसलिए परिभाषित करना __copy__और __deepcopy__निश्चित रूप से आपके लिए जाने का सही तरीका लगता है।

विशेष रूप से, __copy__(उथले प्रति) आपके मामले में बहुत आसान है ...:

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

__deepcopy__समान होगा (एक memoarg को भी स्वीकार करना ), लेकिन वापसी से पहले इसे self.foo = deepcopy(self.foo, memo)किसी भी विशेषता के लिए कॉल करना होगा जो self.fooकि गहरी प्रतिलिपि की आवश्यकता है (अनिवार्य रूप से ऐसे कंटेनर हैं - सूचियां, डाइक, गैर-आदिम ऑब्जेक्ट जो अपने __dict__एस के माध्यम से अन्य सामान रखते हैं )।


1
@kaizer, वे अचार बनाने के साथ-साथ नकल करने के लिए अनुकूलित करने के लिए ठीक हैं, लेकिन यदि आप अचार की परवाह नहीं करते हैं, तो यह उपयोग करने के लिए सरल और अधिक प्रत्यक्ष है __copy__/ __deepcopy__
एलेक्स मार्टेली

4
यह कॉपी / डीपकोपी का सीधा अनुवाद नहीं लगता है। न तो प्रतिलिपि बनाई गई और न ही डीपोस्कोपी द्वारा कॉपी किए जा रहे ऑब्जेक्ट के कंस्ट्रक्टर को कॉल करें। इस उदाहरण पर विचार करें। वर्ग Test1 (ऑब्जेक्ट): init __ (स्व) को परिभाषित करें: "% s।% s"% (स्वयंभू .__ वर्ग .__ name__, " init ") वर्ग Test2 (Test1) प्रिंट करें: __copy __ (स्वयं: नया = प्रकार (स्व) को परिभाषित करें) () नया t1 = Test1 () copy.copy (t1) t2 = Test2 () copy.copy (t2)
Rob Young

12
मुझे लगता है कि टाइप (सेल्फ) के बजाय (), आपको cls = self .__ class__ का उपयोग करना चाहिए; cls .__ new __ (cls) कंस्ट्रक्टर्स इंटरफ़ेस के प्रति असंवेदनशील होना (विशेष रूप से उपवर्ग के लिए)। हालांकि यह वास्तव में महत्वपूर्ण नहीं है।
जुह_

11
क्यों self.foo = deepcopy(self.foo, memo)...? क्या आप वास्तव में मतलब नहीं है newone.foo = ...?
Alois Mahdal

4
@ जुह_ की टिप्पणी हाजिर है। आप कॉल नहीं करना चाहते हैं __init__। वह नकल नहीं करता है। इसके अलावा बहुत बार एक उपयोग का मामला होता है जहां अचार और नकल को अलग करने की आवश्यकता होती है। वास्तव में, मुझे यह भी पता नहीं है कि कॉपी डिफ़ॉल्ट रूप से अचार प्रोटोकॉल का उपयोग करने की कोशिश क्यों करती है। कॉपी-इन-मेमोरी हेरफेर के लिए है, अचार क्रॉस-एपोच दृढ़ता के लिए है; वे पूरी तरह से अलग चीजें हैं जो एक दूसरे से कम संबंध रखती हैं।
निमरोड

96

एलेक्स मार्टेली के जवाब और रोब यंग की टिप्पणी को एक साथ रखते हुए आपको निम्न कोड मिलते हैं:

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

प्रिंट

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

यहाँ वस्तु में अतिरिक्त नकल से बचने के __deepcopy__लिए memoतानाशाही में भर जाता है, जब वस्तु को उसके सदस्य से संदर्भित किया जाता है।


2
@ टायस्टॉर्म क्या है Transporter?
एंटनी हैचकिंस

@AntonyHatchkins Transporterमेरी कक्षा का नाम है जो मैं लिख रहा हूं। उस वर्ग के लिए मैं डीपकोपी व्यवहार को ओवरराइड करना चाहता हूं।
१२

1
@bytormorm की सामग्री क्या है Transporter?
एंटनी हैचकिंस

1
मुझे लगता है कि __deepcopy__अनंत प्रत्यावर्तन से बचने के लिए एक परीक्षण शामिल करना चाहिए: <- भाषा: लैंग-अजगर -> d = आईडी (स्वयं) परिणाम = memo.get (घ, कोई नहीं) करता है, तो परिणाम कोई भी नहीं है: वापसी परिणाम
Antonín Hoskovec

@AntonyHatchkins यह आपके पोस्ट से तुरंत स्पष्ट नहीं है जहां memo[id(self)] वास्तव में अनंत पुनरावृत्ति को रोकने के लिए उपयोग किया जाता है। मैंने एक छोटा उदाहरण एक साथ रखा है जो यह बताता है कि copy.deepcopy()यदि किसी वस्तु की id()कुंजी memoसही है, तो आंतरिक रूप से कॉल को एब्सॉर्ट करता है? यह भी ध्यान देने योग्य है कि deepcopy()यह डिफ़ॉल्ट रूप से अपने दम पर ऐसा लगता है , जो एक ऐसे मामले की कल्पना करना मुश्किल बनाता है जहां __deepcopy__मैन्युअल रूप से परिभाषित करना आवश्यक है ...
जोनाथन एच।

14

पीटर के उत्कृष्ट उत्तर के बाद , कस्टम डीकोस्कोपी को लागू करने के लिए, डिफ़ॉल्ट कार्यान्वयन में न्यूनतम परिवर्तन के साथ (जैसे कि किसी फ़ील्ड को केवल आवश्यकतानुसार संशोधित करना):

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__')तब उपयोग करने के लिए पसंद किया जाता है setattr(self, '__deepcopy__', deepcopy_method)?
जोएल

इस उत्तर के अनुसार , दोनों समान हैं; लेकिन सेटट्रर एक विशेषता सेट करते समय अधिक उपयोगी होता है जिसका नाम डायनामिक है / जो कोडिंग समय पर ज्ञात नहीं है।
ईनो लौर्डिन

8

यह आपकी समस्या से स्पष्ट नहीं है कि आपको इन विधियों को ओवरराइड करने की आवश्यकता क्यों है, क्योंकि आप प्रतिलिपि विधियों के लिए कोई अनुकूलन नहीं करना चाहते हैं।

किसी भी तरह, यदि आप गहरी प्रतिलिपि को अनुकूलित करना चाहते हैं (जैसे कुछ विशेषताओं को साझा करके और दूसरों को कॉपी करके), तो यहां एक समाधान है:

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

क्या क्लोन की भी आवश्यकता नहीं है __deepcopy__क्योंकि इसे विधि रीसेट करना होगा क्योंकि यह __deepcopy__= कोई भी नहीं होगा?
flutefreak7

2
नहीं। यदि __deepcopy__विधि नहीं मिली है (या obj.__deepcopy__कोई नहीं लौटाता है), तो deepcopyमानक डीप-कॉपीिंग फ़ंक्शन पर वापस आ जाता है। इसे यहाँ
पीटर

1
लेकिन तब बी में साझा करने के साथ डीपकोपी करने की क्षमता नहीं होगी? c = deepcopy (a) d = deepcopy (b) से भिन्न होगा क्योंकि d एक डिफ़ॉल्ट डीपोस्कोपी होगा जहां c में a के साथ कुछ साझा किए गए कोड होंगे।
flutefreak7

1
आह, अब मैं देख रहा हूं कि तुम क्या कह रहे हो। अच्छी बात। मैंने इसे ठीक किया, मुझे लगता है, __deepcopy__=Noneक्लोन से नकली विशेषता को हटाकर । नया कोड देखें।
पीटर

1
शायद अजगर विशेषज्ञों के स्पष्ट: के साथ: यदि आप इस कोड अजगर 3 में, परिवर्तन "attr के लिए, shared_attributes.iteritems () में वैल" का उपयोग करता है, तो "attr, shared_attributes.items () में वैल के लिए:"
complexM

6

मैं बारीकियों पर थोड़ा हटकर हो सकता हूं, लेकिन यहां जाता है;

से copyडॉक्स ;

  • उथली प्रतिलिपि एक नई यौगिक वस्तु का निर्माण करती है और फिर (संभव सीमा तक) मूल में पाई जाने वाली वस्तुओं में संदर्भ डालती है।
  • एक गहरी प्रतिलिपि एक नई यौगिक वस्तु का निर्माण करती है और फिर, पुनरावर्ती रूप से, मूल में पाई जाने वाली वस्तुओं में प्रतियां सम्मिलित करती है।

दूसरे शब्दों में: copy()केवल शीर्ष तत्व की नकल करेंगे और बाकी को मूल संरचना में इंगितकर्ताओं के रूप में छोड़ देंगे। deepcopy()फिर से सब कुछ पर कॉपी करेंगे।

यही है, वह है deepcopy()जो आपको चाहिए।

यदि आपको वास्तव में कुछ विशिष्ट करने की आवश्यकता है, तो आप मैनुअल में वर्णित अनुसार ओवरराइड __copy__()या कर सकते हैं __deepcopy__()। व्यक्तिगत रूप से, मैं शायद config.copy_config()इसे सादे बनाने के लिए एक सादे कार्य (जैसे या ऐसे) को लागू करूँगा कि यह पायथन मानक व्यवहार नहीं है।


3
अपनी खुद की कॉपी कार्यान्वयन को परिभाषित करने के लिए एक वर्ग के लिए, यह विशेष विधियों को परिभाषित कर सकता है __copy__() और __deepcopy__() docs.python.org/library/copy.html
SilentGhost

मैं अपना कोड दोबारा जांचूंगा, धन्यवाद। मैं गूंगा महसूस कर रहा हूँ अगर यह कहीं और एक साधारण बग था :-P
ब्रेंट राइट्स कोड

@MortenSiebuhr आप सही हैं। मैं पूरी तरह से स्पष्ट नहीं था कि कॉपी / डीपकोपी डिफ़ॉल्ट रूप से उन कार्यों को ओवरराइड किए बिना कुछ भी करेगा। मैं वास्तविक कोड की तलाश कर रहा था, क्योंकि बाद में मैं ट्वीक कर सकता हूं (जैसे कि अगर मैं सभी विशेषताओं को कॉपी नहीं करना चाहता), तो मैंने आपको एक वोट दिया था, लेकिन मैं @ एलेक्समैर्टिनेली के जवाब के साथ जा रहा हूं। धन्यवाद!
ब्रेंट कोड

2

copyमॉड्यूल अंततः का उपयोग करता है __getstate__()/ नमकीन बनाना प्रोटोकॉल है, तो ये भी ओवरराइड करने के लिए वैध लक्ष्य कर रहे हैं।__setstate__()

डिफ़ॉल्ट कार्यान्वयन बस वापस आ जाता है और __dict__कक्षा के सेट करता है, इसलिए आपको कॉल करने super()और चिंता करने की ज़रूरत नहीं है कि ऊपर , ईनो गॉर्डिन की चतुर चाल ।


1

एंटनी हैचकिंस के स्वच्छ उत्तर पर बिल्डिंग, यहां मेरा संस्करण है जहां प्रश्न में वर्ग एक अन्य कस्टम वर्ग (जिसे हमें कॉल करने की आवश्यकता है 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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.