121

コピーモジュールとの違いを理解してcopyいます。deepcopy私は以前に使用copy.copyして成功しましたが、実際にandメソッドcopy.deepcopyをオーバーロードするのはこれが初めてです。私はすでにグーグルで検索し、組み込みのPythonモジュールを調べて、and関数(、、、など)のインスタンスを探しましたが、それが正しいかどうかはまだ100%わかりません。__copy____deepcopy____copy____deepcopy__sets.pydecimal.pyfractions.py

これが私のシナリオです:

構成オブジェクトがあります。最初に、デフォルトの値のセットを使用して1つの構成オブジェクトをインスタンス化します。この構成は、他の複数のオブジェクトに渡されます(すべてのオブジェクトが同じ構成で開始されるようにするため)。ただし、ユーザーインタラクションが開始されると、各オブジェクトは、互いの構成に影響を与えることなく、その構成を個別に微調整する必要があります(つまり、初期構成のディープコピーを作成して渡す必要があります)。

サンプルオブジェクトは次のとおりです。

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とメソッドを実装する正しい方法は何ですか?deepcopycopy.copycopy.deepcopy

4

10 に答える 10

118

AlexMartelliの回答とRobYoungのコメントを組み合わせると、次のコードが得られます。

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__に、 dictを入力します。memo

于 2013-04-02T20:46:44.800 に答える
92

カスタマイズの推奨事項は、ドキュメントページの最後にあります。

クラスは、ピクルスを制御するために使用するのと同じインターフェースを使用して、コピーを制御できます。これらのメソッドの詳細については、モジュールピクルスの説明を参照してください。コピーモジュールは、copy_reg登録モジュールを使用しません。

クラスが独自のコピー実装を定義するために、特別なメソッド__copy__()と を定義できます__deepcopy__()。前者は、浅いコピー操作を実装するために呼び出されます。追加の引数は渡されません。後者は、ディープコピー操作を実装するために呼び出されます。1つの引数、メモ辞書が渡されます。__deepcopy__() 実装でコンポーネントのディープコピーを作成する必要がある場合はdeepcopy()、コンポーネントを最初の引数として、メモディクショナリを2番目の引数として関数を呼び出す必要があります。

あなたはピクルスのカスタマイズを気にしないように見えるので、定義すること__copy____deepcopy__間違いなくあなたのために行く正しい方法のようです。

具体的には、__copy__(浅いコピー)はあなたの場合はかなり簡単です...:

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

__deepcopy__同様です(引数も受け入れmemoます)が、戻る前に、ディープコピーが必要な属性(基本的にはコンテナーである属性-リスト、dict、sを介して他のものを保持する非プリミティブオブジェクト)を呼び出すself.foo = deepcopy(self.foo, memo)必要self.fooがあります__dict__

于 2009-09-30T21:58:58.043 に答える
19

Peterの優れた回答に従って、デフォルトの実装に最小限の変更を加えてカスタムディープコピーを実装します(たとえば、必要なようにフィールドを変更するだけです)。

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
于 2016-11-08T10:13:07.613 に答える
13

コピーメソッドをカスタマイズしたくないので、なぜこれらのメソッドをオーバーライドする必要があるのか​​は問題から明らかではありません。

とにかく、ディープコピーをカスタマイズしたい場合(たとえば、いくつかの属性を共有し、他の属性をコピーすることによって)、ここに解決策があります:

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
于 2014-07-07T22:53:32.747 に答える
7

詳細については少しずれているかもしれませんが、ここで説明します。

copyドキュメントから;

  • 浅いコピーは、新しい複合オブジェクトを作成し、(可能な範囲で)元のオブジェクトで見つかったオブジェクトへの参照を挿入します。
  • ディープコピーは、新しい複合オブジェクトを作成し、その後、元のオブジェクトで見つかったオブジェクトのコピーを再帰的に挿入します。

言い換えると、:copy()は最上位の要素のみをコピーし、残りは元の構造へのポインターとして残します。deepcopy()すべてを再帰的にコピーします。

つまり、deepcopy()あなたが必要なものです。

本当に具体的なことをする必要がある場合は、マニュアルで説明されているように、__copy__()またはをオーバーライドできます。個人的には、Pythonの標準的な動作ではないことを明確にするために__deepcopy__()、おそらく単純な関数(たとえば、そのようなもの)を実装します。config.copy_config()

于 2009-09-30T21:35:54.360 に答える
3

copyモジュールは最終的に__getstate__()/ピクルスプロトコルを使用するため、これらもオーバーライドする有効なターゲットです。__setstate__()

デフォルトの実装は、クラスのを返し、設定するだけなので、上記のEinoGourdinの巧妙なトリック__dict__を呼び出して心配する必要はありません。super()

于 2018-03-21T16:39:40.347 に答える
2

Antony Hatchkinsの明確な答えに基づいて、問題のクラスが別のカスタムクラスから派生している私のバージョンを次に示します(呼び出す必要があります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
于 2018-01-31T20:28:14.383 に答える
1

PeterEinoGourdinの答えは賢くて便利ですが、非常に微妙なバグがあります。

Pythonメソッドはそれらのオブジェクトにバインドされています。を実行するとcp.__deepcopy__ = deepcopy_method、実際には、元のオブジェクトのオブジェクトcp への参照 が与えられます。を呼び出すと、元のコピーが返されます。 オブジェクトをディープコピーしてからそのコピーをディープコピーすると、出力はコピーのコピーではありません。__deepcopy__ cp.__deepcopy__

動作の最小限の例と、実装をコピー__deepcopy__して新しいオブジェクトにバインドする固定実装を次に示します。

from copy import deepcopy
import types


class Good:
    def __init__(self):
        self.i = 0

    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method
        # Copy the function object
        func = types.FunctionType(
            deepcopy_method.__code__,
            deepcopy_method.__globals__,
            deepcopy_method.__name__,
            deepcopy_method.__defaults__,
            deepcopy_method.__closure__,
        )
        # Bind to cp and set
        bound_method = func.__get__(cp, cp.__class__)
        cp.__deepcopy__ = bound_method

        return cp


class Bad:
    def __init__(self):
        self.i = 0

    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method
        cp.__deepcopy__ = deepcopy_method
        return cp


x = Bad()
copy = deepcopy(x)
copy.i = 1
copy_of_copy = deepcopy(copy)
print(copy_of_copy.i)  # 0

x = Good()
copy = deepcopy(x)
copy.i = 1
copy_of_copy = deepcopy(copy)
print(copy_of_copy.i)  # 1
于 2021-07-21T00:13:29.047 に答える
1

パフォーマンス上の理由でここに来ました。デフォルトのcopy.deepcopy()関数を使用すると、コードが最大30倍遅くなりました。@ Anthony Hatchkins回答を出発点として使用すると、たとえばリストの場合は非常に遅いことに気付きました。リスト全体をコピーするために、ループを単純なスライスに置き換えました。パフォーマンスに関心のある人にとっては、比較を行い、呼び出しをより高速な代替手段に置き換えることは価値があります。copy.deepcopy()setattr[:]timeit.timeit()copy.deepcopy()

setup = 'import copy; l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]'
timeit.timeit(setup = setup, stmt='m=l[:]')
timeit.timeit(setup = setup, stmt='m=l.copy()')
timeit.timeit(setup = setup, stmt='m=copy.deepcopy(l)')

これらの結果が得られます:

0.11505379999289289
0.09126630000537261
6.423627900003339
于 2021-09-10T15:54:30.230 に答える
0

Zach Priceの考えと同様に、その目標を達成するためのより簡単な方法があります。つまり、元の__deepcopy__メソッドのバインドを解除してから、にバインドします。cp

from copy import deepcopy
import types


class Good:
    def __init__(self):
        self.i = 0

    def __deepcopy__(self, memo):
        deepcopy_method = self.__deepcopy__
        self.__deepcopy__ = None
        cp = deepcopy(self, memo)
        self.__deepcopy__ = deepcopy_method
        
        # Bind to cp by types.MethodType
        cp.__deepcopy__ = types.MethodType(deepcopy_method.__func__, cp)

        return cp
于 2022-02-15T11:02:32.710 に答える