programing

Python 개체에 대한 복사/딥 복사 작업을 재정의하는 방법은 무엇입니까?

instargram 2023. 7. 26. 21:35
반응형

Python 개체에 대한 복사/딥 복사 작업을 재정의하는 방법은 무엇입니까?

사이의 차이점을 이해합니다.copy대.deepcopy복사 모듈에 있습니다.사용한 적이 있습니다.copy.copy그리고.copy.deepcopy성공하기 전에는요, 하지만 제가 실제로 과부하를 건 것은 이번이 처음입니다.__copy__그리고.__deepcopy__방법들.저는 이미 웹사이트를 검색하고 내장된 파이썬 모듈을 살펴봄으로써__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나에게 적절한 행동을 주겠습니까?

알렉스 마텔리의 답변과 롭 영의 의견을 종합하면 다음과 같은 코드를 얻을 수 있습니다.

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개체 자체가 구성원으로부터 참조되는 경우 초과 복사를 방지하기 위해 dict.

사용자 지정에 대한 권장 사항은 문서 페이지 맨 끝에 있습니다.

클래스는 피클링을 제어하는 데 사용하는 것과 동일한 인터페이스를 사용하여 복사를 제어할 수 있습니다.이러한 방법에 대한 자세한 내용은 모듈 피클 설명을 참조하십시오.복사 모듈은 copy_reg 등록 모듈을 사용하지 않습니다.

클래스가 자체 복사 구현을 정의하기 위해 특별한 메서드를 정의할 수 있습니다.__copy__()그리고.__deepcopy__()전자는 얕은 복사 작업을 구현하기 위해 호출되며, 추가 인수는 전달되지 않습니다.후자는 딥 복사 작업을 구현하기 위해 호출됩니다. 하나의 인수인 메모리 사전을 전달합니다.만약에__deepcopy__()구현은 구성 요소의 심층적인 복사본을 만들어야 합니다.deepcopy()구성 요소를 첫 번째 인수로 사용하고 메모리 사전을 두 번째 인수로 사용합니다.

커스터마이징을 않는 것처럼 에, 정의하는 은 피링사용지보로므이,.__copy__그리고.__deepcopy__확실히 당신에게 맞는 방법인 것 같습니다.

적으로구체.__copy__(천박한 복사본)은 귀하의 경우에 매우 쉽습니다...:

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

__deepcopy__예: a).memotoo) 를 호출해야 .self.foo = deepcopy(self.foo, memo)의 속성에 떤어특 self.foo복사가 으로 컨테이너인 -- 목록, 딕트,__dict__

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

편집: 이고르 코지렌코가 지적한 이 접근 방식의 한계는 사본이__deepcopy__원본 개체에 계속 바인딩되므로 복사본은 실제로 원본의 복사본이 됩니다.아마도 다시 묶을 수 있는 방법이 있을 것입니다.__deepcopy__cp그것을 그냥 할당하는 대신에.cp.__deepcopy__ = deepcopy_method

복사 방법에 대한 사용자 지정을 원하지 않기 때문에 이러한 방법을 재정의해야 하는 이유가 문제에서 명확하지 않습니다.

어쨌든 딥 카피를 사용자 지정하려면(예: 일부 속성을 공유하고 다른 속성을 복사하여) 다음과 같은 해결책이 있습니다.

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

구체적인 내용에 대해서는 좀 엉뚱할 수도 있지만, 다음과 같습니다.

문서에서;

  • 얕은 복사는 새 복합 객체를 생성한 다음(가능한 범위까지) 원본에서 발견된 객체에 대한 참조를 삽입합니다.
  • 딥 복사본은 새 복합 개체를 생성한 다음 원본에서 발견된 개체의 복사본을 해당 개체에 재귀적으로 삽입합니다.

말로는: 즉, 다과같습다니음다같.copy()맨 위 요소만 복사하고 나머지 요소는 원래 구조물에 포인터로 남깁니다.deepcopy()모든 것 위에 재귀적으로 복사합니다.

그은것,deepcopy()당신에게 필요한 것입니다.

정말로 구체적인 작업이 필요한 경우, 오버라이드할 수 있습니다.__copy__()또는__deepcopy__()설명서에 설명된 바와 같이.는 아마 개으로저일예기인구것능입다니현할을적반는인적예▁((▁a:config.copy_config()Python 표준 동작이 아니라는 것을 분명히 하기 위해서입니다.

Hatchkins의 답변을 지정 된 제 (는 Antony Hatchkins를 부를 가 있습니다, 여다로으탕사의클바른기, 제문가용자스지래정의파생서된래제스클에다있니다합니버전야해화습우전이변을끗는리한깨답s▁building▁antony▁to▁(▁where,s▁version▁clean▁class▁call▁the▁answer▁in합(다▁here▁need'지파다'▁custom니여▁hatch▁from▁another제▁hatch의야전▁derives해▁on기정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

copy 모듈은 결국 __setstate__()/pickling 프로토콜을 사용하므로 이러한 프로토콜도 재정의할 수 있는 유효한 대상입니다.

됩니다.__dict__수업의, 그래서 당신은 전화할 필요가 없습니다.super()그리고 에이노 구딘의 교묘한 속임수에 대해 걱정해, 에서.

피터와 에이노 구딘의 대답은 영리하고 유용하지만, 그들은 매우 미묘한 버그를 가지고 있습니다!

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

Zach Price의 생각과 유사하게, 그 목표를 달성하는 더 간단한 방법이 있습니다. 즉, 원본의 구속을 해제하는 것입니다.__deepcopy__하면 메드를에바니다합딩인소▁it다니에 바인딩됩니다.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

저는 공연을 위해 이곳에 왔습니다. 사용하기copy.deepcopy()함수는 내 코드를 최대 30배까지 느리게 했습니다.@Anthony Hatchkins을 출발점으로 삼아, 저는 다음과 같은 것을 깨달았습니다.copy.deepcopy()목록에 대한 속도가 매우 느립니다.는 교했습다니를 했습니다.setattr 순한루프로 .[:]전체 목록을 복사하기 위한 슬라이싱.이 있는 ▁is▁doingwhile▁worthance▁for를 할 가치가 있습니다.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

언급URL : https://stackoverflow.com/questions/1500718/how-to-override-the-copy-deepcopy-operations-for-a-python-object

반응형