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


101

나는 사이의 차이를 이해 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.copycopy.deepcopy


작동합니까? 문제가 있습니까?
— Ned Batchelder

공유 참조에 여전히 문제가 있다고 생각했지만 다른 곳에서 엉망으로 만들 가능성이 있습니다. 기회가 생기면 @MortenSiebuhr의 게시물을 기반으로 다시 확인하고 결과를 업데이트하겠습니다.
— Brent,

현재 제한된 이해로 인해 copy.deepcopy (ChartConfigInstance)가 원본과 공유 참조가없는 새 인스턴스를 반환 할 것으로 예상합니다 (딥 카피를 직접 다시 구현하지 않고). 이것이 잘못 되었습니까?
— emschorsch

답변:


82

맞춤 설정에 대한 권장 사항은 문서 페이지 의 맨 끝에 있습니다 .

클래스는 동일한 인터페이스를 사용하여 산 세척을 제어하는 ​​데 사용하는 복사를 제어 할 수 있습니다. 이러한 메소드에 대한 정보는 모듈 pickle의 설명을 참조하십시오. 복사 모듈은 copy_reg 등록 모듈을 사용하지 않습니다.

클래스가 자체 복사 구현을 정의하기 위해 특수 메서드 __copy__()및 __deepcopy__(). 전자는 얕은 복사 작업을 구현하기 위해 호출됩니다. 추가 인수가 전달되지 않습니다. 후자는 전체 복사 작업을 구현하기 위해 호출됩니다. 하나의 인수 인 메모 사전이 전달됩니다. 는 IF __deepcopy__() 구현이 구성 요소의 깊은 복사본을 만들 필요가있다, 그것은 호출해야합니다 deepcopy()첫 번째 인자와 두 번째 인수로 메모 사전과 같은 구성 요소와 기능을.

피클 링 커스터마이징에 신경 쓰지 않는 것 같기 때문에 정의 __copy__하고 __deepcopy__확실히 올바른 방법으로 보입니다.

특히, __copy__(얕은 사본) 귀하의 경우에는 매우 쉽습니다 ... :

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

__deepcopy__(A 받아들이는 유사하다 memo너무 인수를)하지만 반환하기 전에 호출해야 self.foo = deepcopy(self.foo, memo)하는 모든 속성에 대한 self.foo요구가 깊은 (컨테이너 본질적 속성을 복사하는 것을 - 목록, dicts을 통해 다른 물건을 개최 비 원시적 객체를 자신의__dict__ 들).


1
@kaizer, 그들은 복사뿐만 아니라 산세 / 산세 제거를 사용자 정의하는 것이 좋지만 산세에 관심이 없다면 __copy__/ 를 사용하는 것이 더 간단하고 직접적 __deepcopy__입니다.
— Alex Martelli

4
그것은 카피 / 딥 카피의 직접적인 번역이 아닌 것 같습니다. 복사 나 딥 카피 모두 복사중인 객체의 생성자를 호출하지 않습니다. 이 예를 고려하십시오. class Test1 (object) : def init __ (self) : print "% s. % s"% (self .__ class .__ name__, " init ") class Test2 (Test1) : def __copy __ (self) : new = type (self) () return new t1 = Test1 () copy.copy (t1) t2 = Test2 () copy.copy (t2)
— Rob Young

12
type (self) () 대신 cls = self .__ class__; cls .__ new __ (cls)는 생성자 인터페이스에 민감하지 않습니다 (특히 서브 클래 싱의 경우). 그러나 여기서는 실제로 중요하지 않습니다.
— Juh_

11
왜 self.foo = deepcopy(self.foo, memo)...? 정말로 의미하지 newone.foo = ...않습니까?
— Alois Mahdal 2013 년

4
@Juh_의 댓글이 있습니다. 에게 전화하고 싶지 않습니다 __init__. 그것은 카피가하는 일이 아닙니다. 또한 산세와 복사가 달라야하는 사용 사례가 매우 자주 있습니다. 사실, 카피가 기본적으로 피클 링 프로토콜을 사용하는 이유조차 모르겠습니다. 복사는 메모리 내 조작을위한 것이고 피클 링은 교차 에포크 지속성을위한 것입니다. 그것들은 서로 거의 관계가없는 완전히 다른 것들입니다.
— Nimrod

97

Alex Martelli의 답변과 Rob Young의 의견을 합치면 다음 코드가 표시됩니다.

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]

여기 에서 객체 자체가 멤버에서 참조되는 경우 과도한 복사를 피하기 위해 dict를 __deepcopy__채 웁니다 memo.


2
@bytestorm은 무엇 Transporter입니까?
— Antony Hatchkins

@AntonyHatchkins Transporter는 내가 작성중인 클래스의 이름입니다. 해당 클래스의 경우 딥 카피 동작을 재정의하고 싶습니다.
— bytestorm

1
@bytestorm의 내용은 Transporter무엇입니까?
— Antony Hatchkins

1
__deepcopy__무한 재귀를 피하기 위해 테스트를 포함해야 한다고 생각 합니다. <!-language : lang-python-> d = id (self) result = memo.get (d, None) if result is None : return result
— Antonín Hoskovec

@AntonyHatchkins 실제로 무한 재귀를 방지하기 위해 사용되는 게시물 에서 즉시 명확하지 않습니다 memo[id(self)]. 내가 함께 넣어 가지고 간단한 예를 제안 copy.deepcopy()자사의 경우 내부적으로 객체에 호출을 중단 id()의 핵심이다 memo올바른을? 또한 가치가 주목 즉 deepcopy()스스로이 작업을 수행 할 것으로 보인다 기본적으로 는 하드 정의하는 경우 상상 할 수있는 __deepcopy__... 수동으로 실제로 필요하다
— 조나단 H

14

다음 베드로의 훌륭한 대답을 기본 구현에 최소한의 변경 (I 필요 같은 예를 들어 그냥 필드를 수정)과, 사용자 정의 deepcopy을 구현하기 위해 :

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__')then 을 사용하는 것보다 선호 setattr(self, '__deepcopy__', deepcopy_method)됩니까?
— joel

이 대답 에 따르면 둘 다 동등합니다. 그러나 setattr은 이름이 동적이거나 코딩시 알려지지 않은 속성을 설정할 때 더 유용합니다.
— Eino Gourdin

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__= None 을 가질 것이기 때문에 메서드 재설정이 필요 하지 않습니까?
— flutefreak7 2017-04-06

2
아니. 경우 __deepcopy__방법을 찾을 수 없습니다 (또는되지 않은 obj.__deepcopy__반환 없음), 다음 deepcopy다시 표준 깊은 복사 기능에 떨어진다. 이 볼 수 있습니다 여기에
— 피터

1
하지만 b는 공유를 통해 딥 카피 할 수 없습니까? c = deepcopy (a)는 d = deepcopy (b)와 다릅니다. d는 c가 a와 일부 공유 속성을 갖는 기본 deepcopy이기 때문입니다.
— flutefreak7

1
아, 이제 무슨 말을하는지 알겠습니다. 좋은 지적. __deepcopy__=None복제에서 가짜 속성을 삭제하여 수정했습니다 . 새 코드를 참조하십시오.
— 피터

1
파이썬 전문가에게 분명 할 수 있습니다. 파이썬 3에서이 코드를 사용한다면 "for attr, val in shared_attributes.iteritems () :"를 "for attr, val in 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
— Brent는

@MortenSiebuhr 당신이 맞습니다. 나는 copy / deepcopy가 그 기능을 재정의하지 않고 기본적으로 아무것도 할 수 있다는 것을 완전히 명확하지 않았습니다. 나중에 조정할 수 있지만 (예 : 모든 속성을 복사하고 싶지 않은 경우) 실제 코드를 찾고 있었기 때문에 찬성 투표를했지만 @AlexMartinelli의 답변으로 갈 것입니다. 감사!
— Brent는

2

copy모듈은 결국 사용 __getstate__()/ 산세 프로토콜 이 또한 재정에 유효한 목표 그래서.__setstate__()

기본 구현 __dict__은 클래스 의 를 반환하고 설정하기 때문에 위super() 의 Eino Gourdin의 영리한 트릭 을 호출 하고 걱정할 필요가 없습니다 .


1

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.