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


101

나는 사이의 차이를 이해 copydeepcopy복사 모듈을. 내가 사용했습니다 copy.copycopy.deepcopy이전을 성공적으로, 그러나 이것은 내가 실제로 과부하에 대해 갔어요 처음 __copy____deepcopy__방법을. 이미 통해 주위 구글에서 보니 한의 인스턴스를 찾기 위해 파이썬 모듈 내장 __copy____deepcopy__기능 (예를 들어 sets.py, decimal.pyfractions.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


작동합니까? 문제가 있습니까?
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.