인스턴스 복사를 직접 다루기

종종 한 인스턴스를 대량으로 복제해서 약간씩 바꿔야할 일이 있습니다.
주로 진화 알고리즘 계열이나 고치기 전/후를 비교해야하거나, 엄청나게 바꾼 뒤에
롤백을 해야한다는 등의 경우가 그런데요.
파이썬에서 복사는 기본 타입들은 각각 독특한 방법이 있지만, 보통은
copy모듈을 쓰면 간단합니다.

그런데 철학적(?)인 분위기를 잡으며 경험이 조금 더 많은 사람이 쓱 나타나서
“너 깊은 복사랑 얕은 복사 알아?”라고 할 것 같은 느낌이 언제나 copy
쓸 때마다 들곤 하죠; 깊은 복사(deep copy)는 참조하고 있는 객체 모두를
복사하기 때문에 속성으로 들어있는 놈들까지 별개의 인스턴스가 돼서
완전히 떨어집니다. 얕은 복사에서는 참조는 복사하지 않고 자신만 복사하기 때문에
리스트같은 것들이 공유가 되는 특징이 있습니다. 물론 얕은 복사가 훨씬 빠르고
자원을 효율적으로 쓰겠죠.

그런데 종종 클래스를 만들다 보면 어떤 것은 깊숙히, 어떤 것은 얕게 해야할 일이
발생하는데요. 그러면 copy.copy를 쓸 수도 없고 copy.deepcopy를 쓸 수도 없고
애매한 상황이 됩니다. 결국은 얕은 복사를 한 다음에 수동으로 깊은 복사를 해 주면
되겠죠. 그래서 좀 더 복사 과정을 자세히 다뤄보도록 하겠습니다.

우선 복사를 하려면 인스턴스를 새로 하나 만들어야 합니다. 여기서 발생하는 문제!
__init__가 호출되면 복사라고 부르기가 애매하겠죠. 이미 초기화된 놈을 다시 초기화하게 되니까..
그래서 __init__는 호출하지 않고 그냥 객체만 생성하는 방법을 써야하는데,
신형 클래스(new style class)냐 구형 클래스(old style class)냐에 따라서 방법이 다릅니다.

신형 클래스는 __new__를 쓰면 됩니다.

구형 클래스에서는 types.InstanceType을 쓰거나 types 모듈 불러오기가 귀찮으면 type(s)
써서 만들 수 있습니다.

그래서 일부만 복사하는 놈을 만들어 보면

이렇게 a는 복사되고, b는 공유되는 방식 copy()를 만들 수 있습니다.

사실 파이썬 자체에서 pickle 모듈같은
곳에서 저장/복원을 위한 속성 저장법이 있는데, copy
모듈도 같은 방법을 지원합니다.

__getstate____setstate__ 중 하나만 구현해도 상관은 없는데 그렇게 하면 리턴값이나 받아오는
값이 모두 딕셔너리가 돼야합니다. 위에서는 a는 그대로 참조만 가져오고, bc는 복사하는
방식으로 클래스 복사 방법을 지정해 줬습니다. 복사와는 크게 관련은 없지만 pickle에서
저장할 수 없는 속성들 (예를 들어 소켓 같은 것)이 들어있는 클래스는 __getstate__를 지정해 주는
방식으로 특정 속성을 저장에서 뺄 수 있습니다.

1 thought on “인스턴스 복사를 직접 다루기”

  1. 나는 주로 __copy__, __deepcopy__를 써왔는데 __getstate__, __setstate__도 가능한 거였군.

Comments are closed.