পাইথন অবজেক্টের অনুলিপি / ডিপকপি অপারেশন কীভাবে ওভাররাইড করা যায়?


104

আমি মধ্যে পার্থক্য বুঝতে copyবনাম deepcopyকপি মডিউলে। আমি সফলভাবে ব্যবহার করেছি copy.copyএবং এর copy.deepcopyআগেও, তবে এটি প্রথম এবং প্রথমবারের মধ্যে আমি পদ্ধতিগুলি __copy__এবং __deepcopy__পদ্ধতিগুলি ওভারলোডিং সম্পর্কে আসলেই চলেছি । আমি ইতিমধ্যে প্রায় googled এবং মাধ্যমে লাগছিল থাকেন বিল্ট-ইন পাইথন মডিউল দৃষ্টান্ত জন্য চেহারা __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 

আমাকে সঠিক আচরণ নিশ্চিত করতে এবং দেওয়ার জন্য এই অবজেক্টের পদ্ধতি copydeepcopyপদ্ধতিগুলি প্রয়োগ করার সঠিক উপায় কী ?copy.copycopy.deepcopy


এটা কি কাজ করে? সমস্যা আছে?
নেড ব্যাচেল্ডার

আমি ভেবেছিলাম আমি এখনও ভাগ করা রেফারেন্সে সমস্যা পাচ্ছি, তবে অন্যত্র যে আমি গণ্ডগোল করেছিলাম তা সম্পূর্ণভাবে সম্ভব। আমি যখন একটি সুযোগ পাবো এবং ফলাফলগুলি আপডেট করব তখন আমি @ মর্টেনসিবুহরের পোস্টের উপর ভিত্তি করে ডাবল চেক করব।
ব্রেন্ট লিখেছেন কোড

আমার বর্তমানে সীমিত বোঝাপড়া থেকে আমি কপি.দীপকপি (ChartConfigInstance) এমন একটি নতুন উদাহরণ ফিরিয়ে আনতে আশা করব যা মূলের সাথে কোনও অংশীদারি রেফারেন্স থাকবে না (নিজেকে ডিপকপির সাথে সংশোধন না করে)। এটা কি ভুল?
emschorsch

উত্তর:


83

কাস্টমাইজ করার জন্য সুপারিশগুলি ডক্স পৃষ্ঠার একেবারে শেষে রয়েছে :

শ্রেণিগুলি পিকিং নিয়ন্ত্রণ করতে ব্যবহৃত অনুলিপি নিয়ন্ত্রণ করতে একই ইন্টারফেসগুলি ব্যবহার করতে পারে। এই পদ্ধতিগুলির তথ্যের জন্য মডিউল আচারের বিবরণ দেখুন। অনুলিপি মডিউলটি কপির_রেজিস্ট্রেশন মডিউল ব্যবহার করে না।

কোনও শ্রেণীর নিজস্ব অনুলিপি প্রয়োগের সংজ্ঞা দেওয়ার জন্য, এটি বিশেষ পদ্ধতিগুলি __copy__()এবং সংজ্ঞা দিতে পারে __deepcopy__()। প্রাক্তনকে অগভীর অনুলিপি অপারেশন বাস্তবায়নের জন্য ডাকা হয়; কোন অতিরিক্ত যুক্তি পাস করা হয়। পরবর্তীকে গভীর অনুলিপি অপারেশন বাস্তবায়নের জন্য ডাকা হয়; এটি একটি তর্ক, মেমো অভিধান পাস করেছে। যদি __deepcopy__() বাস্তবায়নের জন্য কোনও উপাদানটির গভীর অনুলিপি তৈরি করা দরকার হয় deepcopy()তবে এটিটিকে প্রথম যুক্তি হিসাবে উপাদানটির সাথে ফাংশনটি এবং দ্বিতীয় আর্গুমেন্ট হিসাবে মেমো অভিধানকে কল করা উচিত ।

যেহেতু আপনি পিকিং কাস্টমাইজেশনের বিষয়ে যত্ন করছেন না বলে মনে হয় , সংজ্ঞা দেওয়া __copy__এবং __deepcopy__অবশ্যই আপনার পক্ষে যাওয়ার সঠিক উপায় বলে মনে হচ্ছে।

বিশেষত, __copy__(অগভীর অনুলিপি) আপনার ক্ষেত্রে খুব সহজ ...:

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

__deepcopy__অনুরূপ হবে (একটি memoআর্গ গ্রহণ করাও) তবে ফেরার আগে গভীর কপি করা দরকার এমন self.foo = deepcopy(self.foo, memo)কোনও অ্যাট্রিবিউটের জন্য ফোন করতে হবে self.foo(মূলত বৈশিষ্ট্যগুলি যা পাত্রে রয়েছে - তালিকা, ডিক্টস, নন-আদিম বস্তু যা তাদের স্টাফগুলিতে অন্যান্য জিনিস রাখে __dict__)।


4
@ কেজার, তারা পিকিং / আনপিকলিং কাস্টমাইজ করার পাশাপাশি অনুলিপি করা ভাল, তবে আপনি যদি পিকিংয়ের বিষয়ে চিন্তা করেন না তবে এটি সহজ এবং ব্যবহারের জন্য আরও সরাসরি __copy__/ __deepcopy__
অ্যালেক্স মার্তেলি

4
এটি কপি / ডিপকপির সরাসরি অনুবাদ বলে মনে হয় না। অনুলিপি করা বা অবজেক্টের কনস্ট্রাক্টরকে কপি বা ডিপকপি নয়। এই উদাহরণ বিবেচনা করুন। ক্লাস টেস্ট 1 (অবজেক্ট): ডিফ থিম __ (স্ব): মুদ্রণ "% s।% s"% (স্ব .__ শ্রেণি .__ নাম__, " init ") শ্রেণি পরীক্ষা 2 (টেস্ট 1): ডিফ __কপি __ (স্ব): নতুন = টাইপ (স্ব) () নতুন টি 1 = টেস্ট 1 () কপি কোড (টি 1) টি 2 = টেস্ট 2 () কপি কোড (টি 2)
রব ইয়ং

12
আমি মনে করি (স্ব) (টাইপ) এর পরিবর্তে আপনার ক্ল্যাস = স্ব .__ শ্রেণি__ ব্যবহার করা উচিত; cls .__ new __ (cls) কনস্ট্রাক্টর ইন্টারফেসের প্রতি সংবেদনশীল হতে (বিশেষত সাবক্লাসিংয়ের জন্য)। এখানে অবশ্য এটি গুরুত্বপূর্ণ নয়।
জুহ_

11
কেন self.foo = deepcopy(self.foo, memo)...? আসলেই বলতে newone.foo = ...চাও না ?
আলয়েস মাহডাল

4
@ জুহ_-এর মন্তব্য স্পট রয়েছে। আপনি কল করতে চান না __init__। এটি অনুলিপি করে না। এছাড়াও প্রায়শই এমন ব্যবহারের ক্ষেত্রে দেখা যায় যেখানে পিকিং এবং অনুলিপি আলাদা হওয়া দরকার। আসলে, আমি এমনকি কপি কেন ডিফল্টরূপে পিকিং প্রোটোকল ব্যবহার করার চেষ্টা করে তা আমি জানি না। অনুলিপি মেমোরি হেরফের জন্য, পিকিং ক্রস-যুগের অধ্যবসায়ের জন্য; তারা একে অপরের সাথে সামান্য সম্পর্ক বহন করে সম্পূর্ণ আলাদা জিনিস।
নিমরোড

102

অ্যালেক্স মার্তেলির উত্তর এবং রব ইয়ংয়ের মন্তব্য একসাথে রাখলে আপনি নিম্নলিখিত কোডটি পেয়েছেন:

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করে।


4
@ বেস্টর্ম কী Transporter?
অ্যান্টনি হ্যাচকিন্স

@ অ্যান্টনিহ্যাচকিন্স Transporterআমার ক্লাসের নাম যা আমি লিখছি। এই শ্রেণীর জন্য আমি ডিপকপি আচরণটি ওভাররাইড করতে চাই।
বাইস্টর্ম

4
@bytestorm বিষয়বস্তু কি Transporter?
অ্যান্টনি হ্যাচকিন্স 10:25

4
আমার মনে __deepcopy__হয় অসীম পুনরাবৃত্তি এড়াতে একটি পরীক্ষা অন্তর্ভুক্ত করা উচিত: <! - ভাষা: ল্যাং-পাইথন -> ডি = আইডি (স্ব) ফলাফল = মেমোজেট (ডি, কিছুই নয়) যদি ফলাফলটি না হয়: রিটার্ন ফলাফল
আন্তোনন হোসকোভেক

@ অ্যান্টনিহ্যাচকিনস এটি আপনার পোস্ট থেকে তাত্ক্ষণিকভাবে পরিষ্কার নয় যেখানে memo[id(self)] প্রকৃতপক্ষে অসীম পুনরাবৃত্তি রোধ করতে ব্যবহৃত হয়। আমি একটি সংক্ষিপ্ত উদাহরণ একসাথে রেখেছি যা প্রস্তাব করে যে copy.deepcopy()অভ্যন্তরীণভাবে কোনও বস্তুর কলটি বন্ধ করে দেওয়া যদি এটির id()কী memo, সঠিক থাকে? এটিও লক্ষণীয় যে এটি ডিফল্টরূপেdeepcopy() এটি নিজে থেকেই করা সম্ভব হয়েছিল , যা __deepcopy__ম্যানুয়ালি সংজ্ঞা দেওয়ার জন্য আসলে এমন কোনও ক্ষেত্রে কল্পনা করা শক্ত করে তোলে ...
জোনাথন এইচ

15

পিটারের দুর্দান্ত উত্তরের অনুসরণ করে , একটি কাস্টম ডিপকপি বাস্তবায়নের জন্য, ডিফল্ট বাস্তবায়নের ন্যূনতম পরিবর্তন সহ (যেমন, আমার প্রয়োজন মতো একটি ক্ষেত্রটি পরিবর্তন করা):

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

4
এটি কি delattr(self, '__deepcopy__')তখন ব্যবহার করা পছন্দ setattr(self, '__deepcopy__', deepcopy_method)?
জোএল

এই উত্তর অনুযায়ী , উভয়ই সমান; কোডেটিংয়ের সময় নামটি গতিশীল / পরিচিত নয় এমন একটি অ্যাট্রিবিউট সেট করার সময় setattr আরও কার্যকর।
এইনো গৌরদিন

4
এটি আমার ব্যক্তিগত fave এবং আমি এটি উত্পাদনে ব্যবহার করছি যেখানে কোনও বস্তুর লগার রয়েছে, যার পরে থ্রেড লক রয়েছে, যা বাছাই করা যায় না। লগারটি সংরক্ষণ করুন, এটিকে সেট করুন None, অন্য কিছুর জন্য ডিফল্টকে কল করুন এবং তারপরে এটি আবার রেখে দিন। ভবিষ্যত-প্রমাণ কারণ আমার কোনও ক্ষেত্র পরিচালনা করতে ভুলে যাওয়া এবং উত্তরাধিকার সূত্রে প্রাপ্ত ক্লাসগুলি "কেবলমাত্র কাজ করুন" সম্পর্কে চিন্তা করার দরকার নেই।
হারুন ডি Marasco

বিটিডাব্লু আমি এটি ব্যবহার করেছিলাম delattr()এবং এটি পাইথন 2.7 এ ব্যর্থ হয়েছিল AttributeError। "এটিতে সেট করুন None" আমি ব্যবহার করছি using
অ্যারন ডি মারাকো

9

আপনি কেন অনুলিপি পদ্ধতিতে কোনও কাস্টমাইজেশন করতে চান না তাই আপনার সমস্যাগুলি থেকে আপনার কেন এই পদ্ধতিগুলিকে ওভাররাইড করা প্রয়োজন তা পরিষ্কার নয়।

যাইহোক, আপনি যদি ডিপ কপিটি কাস্টমাইজ করতে চান (যেমন কিছু বৈশিষ্ট্য ভাগ করে অন্যকে অনুলিপি করে), তবে এখানে একটি সমাধান রয়েছে:

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

4
নাহ। যদি __deepcopy__পদ্ধতিটি খুঁজে পাওয়া যায় না (বা obj.__deepcopy__কোনওটি প্রত্যাবর্তন করে না), তবে deepcopyস্ট্যান্ডার্ড গভীর-অনুলিপি ফাংশনে ফিরে আসে। এটি এখানে
পিটার

4
তবে খ ভাগ করে নেওয়ার সাথে ডিপকপি করার ক্ষমতা থাকবে না? সি = ডিপকপি (ক) ডি = ডিপকপি (বি) থেকে আলাদা হবে কারণ ডি একটি ডিফল্ট ডিপকপি হবে যেখানে সি এর সাথে কিছু ভাগ করে নেওয়া অ্যাটর থাকবে।
flutefreak7

4
আহ, এখন দেখছি আপনি কি বলছেন। ভাল যুক্তি. আমি এটি __deepcopy__=Noneক্লোন থেকে জাল বৈশিষ্ট্য মুছে ফেলার মাধ্যমে, এটি স্থির করেছি । নতুন কোড দেখুন।
পিটার

4
অজগর বিশেষজ্ঞদের কাছে পরিষ্কার হতে পারে: আপনি যদি অজগর 3 এ এই কোডটি ব্যবহার করেন তবে "অ্যাট্রির জন্য" বদল করুন, শেয়ারড_ট্রিবিউটস.সেটাইটেমগুলিতে ভাল করুন (): "অ্যাট্রির জন্য", শেয়ার্ড_ট্রিবিউটস.ইটিএম (): "
কমপ্লেক্স

6

আমি বিশেষ কিছুটা বন্ধ হতে পারে, কিন্তু এখানে যায়;

copyডক্স থেকে ;

  • একটি অগভীর অনুলিপি একটি নতুন যৌগিক অবজেক্ট তৈরি করে এবং তারপরে (সম্ভাব্য পরিমাণে) মূলটিতে পাওয়া বস্তুগুলির মধ্যে রেফারেন্স সন্নিবেশ করায়।
  • একটি গভীর অনুলিপি একটি নতুন যৌগিক বস্তু তৈরি করে এবং তারপরে, পুনরাবৃত্তভাবে, মূলটিতে পাওয়া বস্তুর মধ্যে অনুলিপি সন্নিবেশ করায়।

অন্য কথায়: copy()কেবলমাত্র শীর্ষ উপাদানটি অনুলিপি করবে এবং বাকীটিকে মূল কাঠামোর মধ্যে পয়েন্টার হিসাবে রেখে দেবে। deepcopy()পুনরাবৃত্তভাবে সব কিছু অনুলিপি করা হবে।

অর্থাৎ deepcopy()আপনার যা প্রয়োজন তা হল।

আপনার যদি সত্যই নির্দিষ্ট কিছু করার দরকার হয় তবে ম্যানুয়ালটিতে বর্ণিত হিসাবে আপনি ওভাররাইড করতে পারেন __copy__()বা করতে পারেন __deepcopy__()। ব্যক্তিগতভাবে, আমি সম্ভবত এটি একটি সরল ফাংশন (উদাহরণস্বরূপ config.copy_config()বা এ জাতীয়) বাস্তবায়িত করব যাতে এটি পাইথন স্ট্যান্ডার্ড আচরণ নয় plain


4
কোনও শ্রেণীর নিজস্ব অনুলিপি প্রয়োগের সংজ্ঞা দেওয়ার জন্য, এটি বিশেষ পদ্ধতিগুলি সংজ্ঞায়িত করতে পারে __copy__() এবং __deepcopy__() docs.python.org/library/copy.html
সাইলেন্টগোস্ট

আমি আমার কোডটি ডাবল চেক করব, ধন্যবাদ। আমি বোবা বোধ করতে যাচ্ছি যদি এটি অন্য কোথাও একটি সাধারণ বাগ ছিল :
ব্রেন্ট রাইটস কোড

@ মর্টেনসিবুহর আমি সম্পূর্ণরূপে পরিষ্কার ছিলাম না যে অনুলিপি / ডিপকপি আমাকে এই ফাংশনগুলিকে ওভাররাইড না করে ডিফল্টরূপে কিছু করতে পারে। আমি আসল কোডটি খুঁজছিলাম যদিও আমি পরে টুইট করতে পারি (উদাহরণস্বরূপ যদি আমি সমস্ত বৈশিষ্ট্য অনুলিপি করতে চাই না), তাই আমি আপনাকে একটি ভোট দিয়েছি তবে আমি @ অ্যালেক্সমার্টিনেলির উত্তরটি দিয়ে যাচ্ছি। ধন্যবাদ!
ব্রেন্ট রাইটিং কোড

2

copyমডিউল ব্যবহার অবশেষে __getstate__()/ pickling প্রোটোকল__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
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.