Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 307 – 피클 프로토콜 확장

Author:
Guido van Rossum, Tim Peters
Status:
Final
Type:
Standards Track
Created:
31-Jan-2003
Python-Version:
2.3
Post-History:
07-Feb-2003

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

서론

Python 2.2에서 신형 객체를 피클링하는 작업은 다소 서투르게 수행되며, 클래식 클래스 인스턴스에 비해 피클 크기가 불어나는 원인이 됩니다. 이 PEP는 이러한 문제와 기타 여러 피클 문제를 해결하는 Python 2.3의 새로운 피클 프로토콜을 문서화합니다.

새로운 피클 프로토콜을 지정하는 데에는 두 가지 측면이 있습니다. 피클된 데이터를 구성하는 바이트 스트림을 지정해야 하며, 객체와 피클링 및 언피클링 엔진 간의 인터페이스도 지정해야 합니다. 이 PEP는 API 문제에 중점을 두지만, 선택을 뒷받침하기 위해 때때로 바이트 스트림 형식의 세부 사항을 다룰 수도 있습니다. 피클 바이트 스트림 형식은 표준 라이브러리 모듈 pickletools.py에 의해 공식적으로 문서화되어 있습니다(Python 2.3용 CVS에 이미 체크인되어 있습니다).

이 PEP는 피클된 객체와 피클링 프로세스 간의 인터페이스를 완전히 문서화하려고 하며, “이 PEP에서 새로 추가됨”이라고 명시하여 추가된 사항을 강조합니다. (피클링 또는 언피클링을 호출하는 인터페이스는 피클러에 피클링 프로토콜을 지정하는 API의 변경 사항을 제외하면 완전히 다루지 않습니다.)

동기

신형 객체를 피클링하면 피클 크기가 심각하게 불어납니다. 예를 들어 다음과 같습니다.:

class C(object): # Omit "(object)" for classic class
    pass
x = C()
x.foo = 42
print len(pickle.dumps(x, 1))

클래식 객체의 바이너리 피클은 33바이트를 차지했지만, 신형 객체의 피클은 86바이트를 차지했습니다.

크기가 불어나는 이유는 복잡하지만, 대부분 신형 객체가 피클될 수 있으려면 __reduce__를 사용해야 한다는 사실 때문입니다. 충분히 검토한 결과, 신형 객체의 피클 크기를 줄이는 유일한 방법은 피클 프로토콜에 새로운 연산 코드를 추가하는 것이라고 결론 내렸습니다. 그 결과 새로운 프로토콜을 사용하면 위 예제의 피클 크기는 35가 됩니다(프로토콜 버전을 나타내기 위해 시작 부분에 2개의 추가 바이트가 사용되지만, 이는 엄밀히 말해 필요하지 않습니다).

프로토콜 버전

이전에는 피클링이 텍스트 모드와 바이너리 모드를 구분했지만, 언피클링은 구분하지 않았습니다. 설계상 바이너리 모드는 텍스트 모드의 상위 집합이며, 언피클러는 수신하는 피클이 텍스트 모드를 사용하는지 바이너리 모드를 사용하는지 미리 알 필요가 없습니다. 언피클링에 사용되는 가상 머신은 모드와 관계없이 동일하며, 특정 연산 코드는 텍스트 모드에서 사용되지 않을 뿐입니다.

소급하여 텍스트 모드는 이제 프로토콜 0, 바이너리 모드는 프로토콜 1이라고 부릅니다. 새로운 프로토콜은 프로토콜 2라고 부릅니다. 피클링 프로토콜의 관례에 따라 프로토콜 2는 프로토콜 1의 상위 집합입니다. 그러나 향후 피클링 프로토콜이 가장 오래된 프로토콜의 상위 집합이어야 하는 상황을 방지하기 위해, 프로토콜 2 피클의 시작 부분에 새로운 연산 코드를 삽입하여 해당 피클이 프로토콜 2를 사용한다는 것을 나타냅니다. 지금까지 Python의 각 릴리스는 모든 이전 릴리스에서 작성된 피클을 읽을 수 있었습니다. 물론 프로토콜 N에서 작성된 피클은 프로토콜 N을 도입한 버전보다 이전 버전의 Python에서는 읽을 수 없습니다.

피클링에 사용되는 여러 함수, 메서드 및 생성자는 이전에 바이너리 모드를 나타내는 플래그인 ‘bin’이라는 위치 인자를 받았으며, 기본값은 0이었습니다. 이 인자의 이름은 ‘protocol’로 변경되며 이제 프로토콜 번호를 나타내고, 기본값은 여전히 0입니다.

이전 Python 버전에서 ‘bin’ 인자에 2를 전달하면 1을 전달한 것과 같은 효과가 발생했습니다. 그럼에도 불구하고 여기에는 특수한 경우가 추가됩니다. 음수를 전달하면 특정 구현에서 지원하는 가장 높은 프로토콜 버전이 선택됩니다. 이 기능은 이전 Python 버전에서도 작동하므로, 하위 호환성과 상위 호환성을 모두 갖춘 방식으로 사용 가능한 가장 높은 프로토콜을 선택하는 데 사용할 수 있습니다. 또한 picklecPickle에서 새로운 모듈 상수 HIGHEST_PROTOCOL을 제공하며, 이 상수는 해당 모듈이 읽을 수 있는 가장 높은 프로토콜 번호와 같습니다. 이는 -1을 전달하는 것보다 더 깔끔하지만 Python 2.3 이전에는 사용할 수 없습니다.

pickle.py 모듈은 위치 인자 대신 키워드 인자로 ‘bin’ 값을 전달하는 것을 지원해 왔습니다. (cPickle은 위치 인자만 허용하므로 권장되지 않지만, 작동하기는 합니다…) ‘bin’을 키워드 인자로 전달하는 것은 더 이상 사용되지 않으며, 이 경우 PendingDeprecationWarning이 발행됩니다. PendingDeprecationWarning메시지를 보려면 -Wa 또는 그 변형을 사용하여 Python 인터프리터를 호출해야 합니다. Python 2.4에서는 경고 클래스가 DeprecationWarning으로 승격될 수 있습니다.

보안 문제

이전 버전의 Python에서는 언피클링 시 특정 작업에 대해 “안전성 검사”를 수행하여, __safe_for_unpickling__속성을 1로 설정했거나 전역 레지스트리인 copy_reg.safe_constructors에 등록된 방식으로 “언피클링에 안전한” 것으로 표시되지 않은 함수나 생성자의 호출을 거부했습니다.

이 기능은 잘못된 보안 의식을 심어 줍니다. 신뢰할 수 없는 피클을 언피클링할 때 원치 않는 코드가 호출될 수 없음을 입증하기 위해 필요한 광범위한 코드 감사를 수행한 사람은 지금까지 아무도 없으며, 실제로 Python 2.2의 pickle.py 모듈에 있는 버그로 인해 이러한 보안 조치를 쉽게 우회할 수 있습니다.

인터넷에서는 구현이 철저히 점검되지 않은 프로토콜을 안전하다고 믿기보다, 자신이 안전하지 않은 프로토콜을 사용하고 있다는 사실을 아는 편이 낫다고 우리는 굳게 믿습니다. 널리 사용되는 고품질 프로토콜의 구현조차도 반복해서 결함이 발견됩니다. Python의 pickle 구현은 훨씬 더 많은 시간과 노력을 투자하지 않고서는 그러한 보장을 할 수 없습니다. 따라서 Python 2.3부터 언피클링에 대한 모든 안전성 검사는 공식적으로 제거되고 다음 경고로 대체됩니다.

Warning

신뢰할 수 없거나 인증되지 않은 출처에서 받은 데이터를 언피클링하지 마십시오.

안전성 검사가 존재하더라도 동일한 경고가 이전 Python 버전에도 적용됩니다.

확장된 __reduce__ API

클래스가 피클링을 제어하는 데 사용할 수 있는 API에는 여러 가지가 있습니다. 아마도 이 중 가장 널리 사용되는 것은 __getstate____setstate__일 것입니다. 하지만 가장 강력한 것은 __reduce__입니다. (__getinitargs__도 있으며, 아래에서 __getnewargs__를 추가합니다.)

__reduce__기능을 제공하는 방법에는 여러 가지가 있습니다. 클래스가 __reduce__ 메서드나 __reduce_ex__ 메서드(다음 절 참조)를 구현할 수도 있고, copy_reg에 reduce 함수를 선언할 수도 있습니다(copy_reg.dispatch_table은 클래스를 함수에 매핑합니다). 하지만 반환값은 정확히 동일한 방식으로 해석되며, 여기서는 이를 통칭하여 __reduce__라고 하겠습니다.

중요: 클래식 클래스 인스턴스의 피클링에서는 __reduce__또는 __reduce_ex__메서드나 copy_reg 디스패치 테이블의 reduce 함수를 찾지 않으므로, 클래식 클래스는 여기서 의도하는 의미의 __reduce__기능을 제공할 수 없습니다. 클래식 클래스는 피클링을 사용자 지정하려면 __getinitargs__및/또는 __getstate__를 사용해야 합니다. 이에 대해서는 아래에서 설명합니다.

__reduce__는 문자열 또는 튜플을 반환해야 합니다. 문자열을 반환하는 경우, 이는 상태를 피클링하지 않고 이름으로 참조되는 동등한 객체에 대한 참조로 처리되는 객체입니다. 놀랍게도 __reduce__가 반환하는 문자열은 객체의 로컬 이름(해당 모듈을 기준으로 한 이름)이어야 합니다. pickle 모듈은 객체가 속한 모듈을 확인하기 위해 모듈 네임스페이스를 검색합니다.

이 절의 나머지 부분에서는 __reduce__가 반환하는 튜플을 다룹니다. 이 튜플은 크기가 가변적이며 길이는 2에서 5까지입니다. 처음 두 항목(함수와 인자)은 필수입니다. 나머지 항목은 선택 사항이며 뒤쪽부터 생략할 수 있습니다. 선택적 항목의 값으로 None을 지정하는 것은 해당 항목을 생략하는 것과 동일하게 작동합니다. 마지막 두 항목은 이 PEP에서 새로 추가되었습니다. 항목은 다음 순서로 나열됩니다.

function Required.

A callable object (not necessarily a function) called to create the initial version of the object; state may be added to the object later to fully reconstruct the pickled state. This function must itself be picklable. See the section about __newobj__ for a special case (new in this PEP) here.

arguments Required.

A tuple giving the argument list for the function. As a special case, designed for Zope 2’s ExtensionClass, this may be None; in that case, function should be a class or type, and function.__basicnew__() is called to create the initial version of the object. This exception is deprecated.

언피클링은 function(*arguments)을 호출하여 초기 객체를 생성하며, 아래에서는 이 객체를 obj라고 부릅니다. 나머지 항목을 생략하면 이 객체에 대한 언피클링은 끝나고 obj가 결과가 됩니다. 그렇지 않으면 아래와 같이 지정된 각 항목에 의해 언피클링 시 obj가 수정됩니다.

state Optional.

Additional state. If this is not None, the state is pickled, and obj.__setstate__(state) will be called when unpickling. If no __setstate__ method is defined, a default implementation is provided, which assumes that state is a dictionary mapping instance variable names to their values. The default implementation calls

obj.__dict__.update(state)

or, if the update() call fails,

for k, v in state.items():
    setattr(obj, k, v)
listitems Optional, and new in this PEP.

If this is not None, it should be an iterator (not a sequence!) yielding successive list items. These list items will be pickled, and appended to the object using either obj.append(item) or obj.extend(list_of_items). This is primarily used for list subclasses, but may be used by other classes as long as they have append() and extend() methods with the appropriate signature. (Whether append() or extend() is used depends on which pickle protocol version is used as well as the number of items to append, so both must be supported.)

dictitems Optional, and new in this PEP.

If this is not None, it should be an iterator (not a sequence!) yielding successive dictionary items, which should be tuples of the form (key, value). These items will be pickled, and stored to the object using obj[key] = value. This is primarily used for dict subclasses, but may be used by other classes as long as they implement __setitem__.

참고: Python 2.2 및 그 이전 버전에서 cPickle을 사용할 때는 상태가 존재하면 그 값이 None인 경우에도 상태가 피클링되었습니다. __setstate__호출을 피하는 유일하게 안전한 방법은 __reduce__에서 2-튜플을 반환하는 것이었습니다. (하지만 pickle.py는 상태가 None인 경우 상태를 피클링하지 않았습니다.) Python 2.3에서는 피클링 시 __reduce__가 값이 None인 상태를 반환하면 언피클링 시 __setstate__가 호출되지 않습니다.

Python 2.2와 Python 2.3 모두에서 작동해야 하는 __reduce__ 구현은 listitemsdictitems 기능을 사용할 수 있는지 확인하기 위해 pickle.format_version변수를 검사할 수 있습니다. 이 값이 >= "2.0"이상이면 해당 기능이 지원됩니다. 그렇지 않으면 모든 리스트 또는 딕셔너리 항목을 어떤 방식으로든 ‘state’ 반환 값에 포함해야 하며, __setstate__메서드는 상태의 일부로 리스트 또는 딕셔너리 항목을 받을 수 있도록 준비해야 합니다(이를 수행하는 방법은 애플리케이션에 달려 있습니다).

__reduce_ex__ API입니다.

__reduce__를 구현할 때 프로토콜 버전을 알아야 하는 경우가 있습니다. __reduce__대신 __reduce_ex__라는 메서드를 구현하면 됩니다. __reduce_ex__가 존재하면 __reduce__보다 우선하여 호출됩니다(하위 호환성을 위해 __reduce__를 계속 제공해도 됩니다). __reduce_ex__ 메서드는 프로토콜 버전인 단일 정수 인자를 사용하여 호출됩니다.

‘object’ 클래스는 __reduce____reduce_ex__를 모두 구현합니다. 그러나 서브클래스가 __reduce__를 재정의하고 __reduce_ex__는 재정의하지 않으면, __reduce_ex__구현은 이를 감지하고 __reduce__를 호출합니다.

__reduce__ 구현이 없는 경우의 피클링 사용자 지정입니다.

특정 클래스에 사용할 수 있는 __reduce__ 구현이 없으면 서로 다르게 처리되므로 별도로 고려해야 하는 세 가지 경우가 있습니다:

  1. 모든 프로토콜의 클래식 클래스 인스턴스입니다.
  2. 프로토콜 0 및 1의 뉴 스타일 클래스 인스턴스입니다.
  3. 프로토콜 2의 뉴 스타일 클래스 인스턴스입니다.

C로 구현된 타입은 뉴 스타일 클래스로 간주됩니다. 그러나 공통 내장 타입을 제외하면, 이러한 타입은 프로토콜 0 또는 1로 피클링할 수 있으려면 __reduce__구현을 제공해야 합니다. 프로토콜 2에서는 __getnewargs__, __getstate____setstate__를 제공하는 내장 타입도 지원됩니다.

사례 1: 클래식 클래스 인스턴스 피클링입니다.

이 경우는 모든 프로토콜에서 동일하며 Python 2.1 이후로 변경되지 않았습니다.

클래식 클래스에서는 __reduce__가 사용되지 않습니다. 대신 클래식 클래스는 __getstate__, __setstate____getinitargs__라는 메서드를 제공하여 피클링을 사용자 지정할 수 있습니다. 이러한 메서드가 없으면 모든 인스턴스 변수를 피클링할 수 있는 한 작동하는 클래식 클래스 인스턴스용 기본 피클링 전략이 구현됩니다. 이 기본 전략은 __getstate____setstate__의 기본 구현을 기준으로 문서화되어 있습니다.

클래식 클래스 인스턴스의 피클링을 사용자 지정하는 주된 방법은 __getstate__ 및/또는 __setstate__메서드를 지정하는 것입니다. 클래스가 이 중 하나만 구현하고 다른 하나는 구현하지 않아도 기본 버전과 호환되기만 하면 괜찮습니다.

__getstate__ 메서드입니다.

__getstate__ 메서드는 객체 자체를 참조하지 않고 객체의 상태를 나타내는 피클링 가능한 값을 반환해야 합니다. __getstate__ 메서드가 없으면 self.__dict__를 반환하는 기본 구현이 사용됩니다.

__setstate__ 메서드입니다.

__setstate__ 메서드는 하나의 인자를 받아야 하며, __getstate__(또는 해당 기본 구현)가 반환한 값과 함께 호출됩니다.

__setstate__ 메서드가 없으면 상태가 인스턴스 변수 이름을 값에 매핑하는 딕셔너리라고 가정하는 기본 구현이 제공됩니다. 기본 구현은 두 가지를 시도합니다:

  • 먼저 self.__dict__.update(state)를 호출하려고 시도합니다.
  • update() 호출이 RuntimeError 예외로 실패하면, 상태 딕셔너리의 각 (key, value) 쌍에 대해 setattr(self, key, value)를 호출합니다. 이는 제한된 실행 모드에서 언피클링할 때만 발생합니다(rexec 표준 라이브러리 모듈을 참조하십시오).

__getinitargs__ 메서드

__setstate__ 메서드(또는 기본 구현)는 해당 객체의 __setstate__ 메서드를 호출할 수 있도록 새 객체가 이미 존재할 것을 요구합니다. 핵심은 완전히 초기화되지 않은 새 객체를 만드는 것입니다. 특히 가능하다면 클래스의 __init__ 메서드를 호출해서는 안 됩니다.

가능한 방법은 다음과 같습니다.

  • 일반적으로 다음과 같은 방법을 사용합니다. 사소한 클래식 클래스(메서드나 인스턴스 변수가 없는 클래스)의 인스턴스를 만든 다음, __class__ 할당을 사용하여 해당 클래스의 클래스를 원하는 클래스로 변경합니다. 이렇게 하면 __init__이 호출되지 않은, 비어 있는 __dict__를 가진 원하는 클래스의 인스턴스가 생성됩니다.
  • 그러나 클래스에 __getinitargs__라는 메서드가 있으면 위 방법을 사용하지 않고, __getinitargs__가 반환한 튜플을 클래스 생성자의 인자 목록으로 사용하여 클래스 인스턴스를 생성합니다. 이 작업은 __getinitargs__가 빈 튜플을 반환하더라도 수행합니다 — __getinitargs__ 메서드가 ()를 반환하는 것은 __getinitargs__가 전혀 없는 것과 동일하지 않습니다. __getinitargs__반드시 튜플을 반환해야 합니다.
  • 제한된 실행 모드에서는 첫 번째 항목의 방법이 작동하지 않습니다. 이 경우 __getinitargs__ 메서드가 없으면 빈 인자 목록으로 클래스 생성자를 호출합니다. 이는 제한 실행 모드에서 고전 클래스가 언피클될 수 없도록 하려면 __getinitargs__를 구현하거나 해당 생성자(즉, 해당 __init__ 메서드)가 인자 없이 호출 가능 객체여야 한다는 의미입니다.

사례 2: 프로토콜 0 또는 1을 사용하여 새 스타일 클래스 인스턴스 피클링

이 사례는 Python 2.2에서 변경되지 않았습니다. 하위 호환성이 문제가 되지 않을 때 새 스타일 클래스 인스턴스를 더 잘 피클링하려면 프로토콜 2를 사용해야 합니다. 아래의 사례 3을 참조하십시오.

C 또는 Python으로 구현된 새 스타일 클래스는 범용 베이스 클래스인 ‘object’에서 기본 __reduce__ 구현을 상속합니다.

이 기본 __reduce__ 구현은 pickle 모듈이 내장 지원을 제공하는 내장 타입에는 사용되지 않습니다. 해당 타입의 전체 목록은 다음과 같습니다.

  • 구체적인 내장 타입은 NoneType, bool, int, float, complex, str, unicode, tuple, list, dict입니다. (복소수는 copy_reg에 등록된 __reduce__ 구현 덕분에 지원됩니다.) Jython에서는 PyStringMap도 이 목록에 포함됩니다.
  • 클래식 인스턴스입니다.
  • 클래식 클래스 객체, Python 함수 객체, 내장 함수 및 메서드 객체, 새 스타일 타입 객체(== 새 스타일 클래스 객체)입니다. 이러한 객체는 값이 아니라 이름으로 피클링됩니다. 언피클링할 때 동일한 이름(정규화된 모듈 이름과 해당 모듈의 변수 이름을 합친 것)을 가진 객체에 대한 참조로 대체됩니다.

위에 언급되지 않은 내장 타입과 C로 구현된 새 스타일 클래스의 경우 피클링 시 기본 __reduce__ 구현이 실패합니다. 피클링 가능하게 하려면 프로토콜 0 및 1에서 사용자 지정 __reduce__ 구현을 제공해야 합니다.

Python으로 구현된 새 스타일 클래스의 경우 기본 __reduce__ 구현(copy_reg._reduce)은 다음과 같이 작동합니다.

피클링할 객체의 클래스를 D라고 합시다. 먼저 C로 구현된 가장 가까운 베이스 클래스를 찾습니다(내장 타입이거나 확장 클래스로 정의된 타입입니다). 이 베이스 클래스를 B라고 하고, 피클링할 객체의 클래스를 D라고 합니다. B가 ‘object’ 클래스가 아니라면, B 클래스의 인스턴스는 내장 지원을 받거나(위의 세 항목에 정의된 내용에 따름) 기본값이 아닌 __reduce__ 구현을 가져 피클링 가능해야 합니다. BD와 동일한 클래스여서는 안 됩니다(동일하다면 D가 Python으로 구현된 것이 아님을 의미합니다).

기본 __reduce__가 생성하는 호출 가능 객체는 copy_reg._reconstructor이고, 그 인자 튜플은 (D, B, basestate)이며, B가 내장 object 클래스인 경우 basestateNone이고, basestate

basestate = B(obj)

B가 내장 object 클래스가 아닌 경우입니다. 이는 내장 타입의 서브클래스를 피클링하는 데 맞춰진 것입니다. 예를 들어 list(some_list_subclass_instance)list 서브클래스 인스턴스의 “list 부분”을 생성합니다.

언피클링 시 객체는 다음과 같이 copy_reg._reconstructor에 의해 다시 생성됩니다.:

obj = B.__new__(D, basestate)
B.__init__(obj, basestate)

기본 __reduce__ 구현을 사용하는 객체는 __getstate__ 및/또는 __setstate__ 메서드를 정의하여 이를 사용자 정의할 수 있습니다. 이러한 메서드는 위에서 설명한 클래식 클래스의 경우와 거의 동일하게 동작하지만, __getstate__가 값이 거짓으로 간주되는 객체(유형과 관계없이)를 반환하는 경우(예: None, 0인 숫자, 빈 시퀀스 또는 매핑)에는 이 상태가 피클링되지 않으며 __setstate__는 전혀 호출되지 않습니다. __getstate__가 존재하고 참인 값을 반환하면, 해당 값은 기본 __reduce__가 반환하는 튜플의 세 번째 요소가 되며, 역피클링 시 해당 값이 __setstate__에 전달됩니다. __getstate__가 존재하지 않지만 obj.__dict__가 존재하면, obj.__dict____reduce__가 반환하는 튜플의 세 번째 요소가 되며, 다시 역피클링 시 해당 값이 obj.__setstate__로 전달됩니다. 기본 __setstate__는 위에서 설명한 클래식 클래스의 경우와 동일합니다.

이 전략은 슬롯을 무시한다는 점에 유의하십시오. 슬롯은 있지만 __getstate__ 메서드가 없는 신식 클래스의 인스턴스는 프로토콜 0 및 1로 피클링할 수 없으며, 코드는 이 조건을 명시적으로 확인합니다.

신식 클래스 인스턴스를 피클링할 때는 __getinitargs__가 존재하더라도(모든 프로토콜에서) 이를 무시한다는 점에 유의하십시오. __getinitargs__는 클래식 클래스에서만 유용합니다.

사례 3: 프로토콜 2를 사용한 신식 클래스 인스턴스의 피클링

프로토콜 2에서는 ‘object’ 기본 클래스에서 상속된 기본 __reduce__ 구현이 무시됩니다. 대신 다른 기본 구현이 사용되며, 이 구현을 통해 프로토콜 0 또는 1에서 가능한 것보다 신식 클래스 인스턴스를 더 효율적으로 피클링할 수 있지만 Python 2.2와의 하위 호환성을 희생합니다(즉, 프로토콜 2 피클은 Python 2.3 이전 버전에서 역피클링할 수 없다는 의미일 뿐입니다).

사용자 정의에는 세 가지 특수 메서드인 __getstate__, __setstate____getnewargs__가 사용됩니다(__getinitargs__는 다시 무시된다는 점에 유의하십시오). 클래스가 이러한 메서드 중 하나 이상을 구현하되 모두 구현하지 않아도 괜찮으며, 기본 구현과 호환되기만 하면 됩니다.

__getstate__ 메서드

__getstate__ 메서드는 객체 자체를 참조하지 않으면서 객체의 상태를 나타내는 피클링 가능한 값을 반환해야 합니다. __getstate__ 메서드가 존재하지 않으면 아래에 설명된 기본 구현이 사용됩니다.

여기에는 클래식 클래스와 신식 클래스 사이에 미묘한 차이가 있습니다. 클래식 클래스의 __getstate__None을 반환하면 역피클링 과정의 일부로 self.__setstate__(None)이 호출됩니다. 그러나 신식 클래스의 __getstate__None을 반환하면 역피클링 과정에서 해당 클래스의 __setstate__는 전혀 호출되지 않습니다.

__getstate__ 메서드가 존재하지 않으면 기본 상태가 계산됩니다. 여기에는 몇 가지 경우가 있습니다.

  • 인스턴스 __dict__가 없고 __slots__도 없는 신식 클래스의 경우 기본 상태는 None입니다.
  • 인스턴스 __dict__가 있고 __slots__는 없는 신식 클래스의 경우 기본 상태는 self.__dict__입니다.
  • 인스턴스 __dict____slots__가 모두 있는 신식 클래스의 경우 기본 상태는 두 개의 딕셔너리로 구성된 튜플입니다. 하나는 self.__dict__이고, 다른 하나는 슬롯 이름을 슬롯 값에 매핑하는 딕셔너리입니다. 후자의 딕셔너리에는 값이 있는 슬롯만 포함됩니다.
  • __slots__가 있고 인스턴스 __dict__가 없는 신식 클래스의 경우 기본 상태는 첫 번째 항목이 None이고 두 번째 항목이 앞 항목에서 설명한 슬롯 이름과 슬롯 값을 매핑하는 딕셔너리인 튜플입니다.

__setstate__ 메서드

__setstate__ 메서드는 인자를 하나 받아야 하며, __getstate__에서 반환된 값 또는 __getstate__ 메서드가 정의되지 않은 경우 위에서 설명한 기본 상태를 인자로 호출됩니다.

__setstate__ 메서드가 존재하지 않으면 위에서 설명한 기본 __getstate__가 반환하는 상태를 처리할 수 있는 기본 구현이 제공됩니다.

__getnewargs__ 메서드

고전 클래스와 마찬가지로 __setstate__ 메서드(또는 해당 기본 구현)를 사용하려면 새 객체가 이미 존재해야 하며, 그래야 해당 __setstate__ 메서드를 호출할 수 있습니다.

프로토콜 2에서는 다음과 같이 새 객체를 생성하게 하는 새로운 피클링 opcode가 사용됩니다.:

obj = C.__new__(C, *args)

여기서 C는 피클된 객체의 클래스이고, args는 빈 튜플이거나, 정의되어 있다면 __getnewargs__ 메서드가 반환하는 튜플입니다. __getnewargs__는 튜플을 반환해야 합니다. __getnewargs__ 메서드가 없는 것은 ()를 반환하는 메서드가 있는 것과 같습니다.

__newobj__ 언피클링 함수

__reduce__가 반환한 언피클링 함수(반환된 튜플의 첫 번째 항목)의 이름이 __newobj__인 경우, 피클 프로토콜 2에서는 특별한 일이 발생합니다. __newobj__라는 이름의 언피클링 함수는 다음 의미를 갖는 것으로 간주합니다.:

def __newobj__(cls, *args):
    return cls.__new__(cls, *args)

피클 프로토콜 2는 이 이름을 가진 언피클링 함수를 특별히 처리하며, ‘cls’와 ‘args’가 주어졌을 때 cls.__new__(cls, *args)를 반환하는 피클링 오피코드를 생성합니다. 이때 __newobj__에 대한 참조도 함께 피클링하지 않습니다(이는 __reduce__ 구현이 없을 때 새 스타일 클래스 인스턴스에 대해 프로토콜 2가 사용하는 것과 동일한 피클링 오피코드입니다). 이것이 프로토콜 2 피클이 클래식 피클보다 훨씬 작은 주된 이유입니다. 물론 피클링 코드는 __newobj__라는 이름의 함수가 실제로 예상한 의미를 갖는지 검증할 수 없습니다. 다른 것을 반환하는 __newobj__라는 이름의 언피클링 함수를 사용한다면, 그 결과를 감수해야 합니다.

Python 2.2에서 이 기능을 사용해도 안전합니다. 권장되는 __newobj__구현에는 Python 2.3에 의존하는 내용이 없습니다.

확장 레지스트리

프로토콜 2는 피클의 크기를 줄이는 새로운 메커니즘을 지원합니다.

클래스 인스턴스(클래식 또는 새 스타일)를 피클링하면 클래스의 전체 이름(패키지 이름을 포함한 모듈 이름과 클래스 이름)이 피클에 포함됩니다. 특히 작은 피클을 많이 생성하는 애플리케이션에서는 각 피클에 반복해서 포함해야 하는 오버헤드가 상당히 큽니다. 큰 피클의 경우 프로토콜 1을 사용하면 동일한 클래스 이름에 대한 반복 참조가 “memo” 기능을 사용하여 압축됩니다. 그러나 각 클래스 이름은 피클마다 적어도 한 번은 전체 이름으로 기록해야 하므로, 작은 피클에서는 오버헤드가 상당히 커집니다.

확장 레지스트리를 사용하면 가장 자주 사용되는 이름을 작은 정수로 나타낼 수 있으며, 이러한 정수는 매우 효율적으로 피클링됩니다. 1–255 범위의 확장 코드는 오피코드를 포함해 2바이트만 필요하고, 256–65535 범위의 확장 코드는 오피코드를 포함해 3바이트만 필요합니다.

피클 프로토콜의 설계 목표 중 하나는 피클을 “context-free”로 만드는 것입니다. 피클이 참조하는 클래스를 포함한 모듈을 설치해 두기만 하면 해당 클래스를 미리 import하지 않아도 피클을 언피클할 수 있습니다.

확장 코드를 무분별하게 사용하면 피클의 이러한 바람직한 속성이 위태로워질 수 있습니다. 따라서 확장 코드의 주된 사용처는 표준 제정 기관이 표준화할 코드 집합으로 제한됩니다. Python의 경우 표준 제정 기관은 PSF입니다. PSF는 때때로 확장 코드와 클래스 이름을 매핑하는 표를 결정합니다(간혹 다른 전역 객체의 이름도 포함되며, 함수도 대상이 될 수 있습니다). 이 표는 다음 Python 릴리스에 포함됩니다.

그러나 Zope와 같은 일부 애플리케이션에서는 컨텍스트가 필요 없는 피클이 필수 사항이 아니므로, PSF가 일부 코드를 표준화할 때까지 기다리는 것이 현실적이지 않을 수 있습니다. 이러한 애플리케이션을 위해 두 가지 해결책이 제공됩니다.

첫째, 몇 가지 확장 코드 범위는 사적 사용을 위해 예약되어 있습니다. 모든 애플리케이션은 이러한 범위에 코드를 등록할 수 있습니다. 이러한 범위의 코드를 사용하여 피클을 교환하는 두 애플리케이션은 확장 코드와 이름 간의 매핑에 합의하기 위한 대역 외 메커니즘을 갖추어야 합니다.

둘째, 일부 대규모 Python 프로젝트(예: Zope)에는 “private use” 범위 밖의 확장 코드 범위를 할당할 수 있으며, 해당 프로젝트는 이를 원하는 대로 할당할 수 있습니다.

확장 레지스트리는 확장 코드와 이름 간의 매핑으로 정의됩니다. 확장 코드를 언피클하면 결국 객체가 생성되지만, 이 객체는 이름을 모듈 이름과 그 뒤에 오는 클래스(또는 함수) 이름으로 해석하여 가져옵니다. 이름에서 객체로의 매핑은 캐시됩니다. 특정 이름을 import할 수 없는 경우도 충분히 있을 수 있지만, 그러한 이름에 대한 참조를 포함한 피클을 언피클해야 하는 경우가 아니라면 문제가 되지 않습니다. (프로토콜 0 또는 1을 사용하는 피클에서 그러한 이름을 직접 참조할 때도 이미 동일한 문제가 존재합니다.)

다음은 확장 코드 범위의 제안된 초기 할당입니다:

처음 마지막 개수 용도
0 0 1 예약되어 있으며 — 절대 사용되지 않습니다
1 127 127 Python 표준 라이브러리용으로 예약되어 있습니다
128 191 64 Zope용으로 예약되어 있습니다
192 239 48 제3자용으로 예약되어 있습니다
240 255 16 개인 사용을 위해 예약되어 있으며 (절대 할당되지 않습니다)
256 MAX MAX 향후 할당용으로 예약되어 있습니다

MAX는 2147483647 또는 2**31-1을 의미합니다. 이는 현재 정의된 프로토콜의 엄격한 제한입니다.

현재로서는 특정 확장 코드가 아직 할당되지 않았습니다.

확장 레지스트리 API

확장 레지스트리는 copy_reg 모듈의 비공개 전역 변수로 유지됩니다. 다음 세 함수는 이 모듈에서 레지스트리를 조작하기 위해 정의되어 있습니다.

add_extension(module, name, code)
확장 코드를 등록합니다. modulename 인자는 문자열이어야 하며, code는 1부터 MAX까지의 범위(양 끝 포함)에 속하는 int여야 합니다. 이는 새로운 (module, name) 쌍을 새 코드에 등록하는 것이거나, remove_extension() 호출로 취소되지 않은 이전 호출의 중복 반복이어야 합니다. (module, name) 쌍은 둘 이상의 코드에 매핑될 수 없으며, 코드 역시 둘 이상의 (module, name) 쌍에 매핑될 수 없습니다.
remove_extension(module, name, code)
인자는 add_extension()과 동일합니다. 이전에 등록된 (module, name)code 간의 매핑을 제거합니다.
clear_extension_cache()
확장 코드의 구현은 자주 이름이 지정되는 객체를 빠르게 로드하기 위해 캐시를 사용할 수 있습니다. 이 메서드를 호출하면 이 캐시를 비울 수 있습니다(캐시된 객체에 대한 참조 제거).

이 API는 표준 범위 할당을 강제하지 않는다는 점에 유의하십시오. 이를 준수하는 것은 애플리케이션의 몫입니다.

copy 모듈

전통적으로 copy 모듈은 copy()deepcopy() 연산을 커스터마이즈하기 위해 피클링 API의 확장된 서브셋을 지원해 왔습니다.

특히 __copy__ 또는 __deepcopy__ 메서드를 확인하는 것 외에도, copy()deepcopy()는 항상 __reduce__를 찾았으며, 클래식 클래스의 경우 __getinitargs__, __getstate__, __setstate__를 찾았습니다.

Python 2.2에서는 ‘object’로부터 상속된 기본 __reduce__가 단순한 새 스타일 클래스의 복사를 가능하게 했지만, 슬롯과 그 밖의 다양한 특수 사례는 다루지 못했습니다.

Python 2.3에서는 copy 모듈에 여러 변경 사항이 적용되었습니다.

  • __reduce_ex__가 지원됩니다(항상 프로토콜 버전 인자로 2를 사용하여 호출됩니다).
  • __reduce__의 4개 및 5개 인자 반환값이 지원됩니다.
  • __reduce__ 메서드를 찾기 전에, 피클링에서와 마찬가지로 copy_reg.dispatch_table이 참조됩니다.
  • __reduce__ 메서드가 object로부터 상속된 경우, 이는 (무조건적으로) 피클 프로토콜 2와 동일한 API인 __getnewargs__, __getstate__, __setstate__를 사용하며 listdict 서브클래스 및 슬롯을 처리하는 더 나은 메서드로 대체됩니다.

이 후자의 변경으로 인해, Python 2.2에서는 복사 가능했던 일부 새 스타일 클래스가 Python 2.3에서는 복사 불가능하게 됩니다(이러한 클래스는 피클 프로토콜 2로도 피클링할 수 없습니다). 이러한 클래스의 최소 예제는 다음과 같습니다.:

class C(object):
    def __new__(cls, a):
        return object.__new__(cls)

이 문제는 __new__가 재정의되어 클래스 인자 외에 적어도 하나의 필수 인자를 가질 때만 발생합니다.

이를 해결하려면 클래스를 제외한 적절한 인자 튜플을 반환하는 __getnewargs__ 메서드를 추가해야 합니다.

Python long 피클링

프로토콜 0과 1에서 Python long을 피클링하고 언피클링하는 데는 자릿수의 제곱에 비례하는 시간이 걸립니다. 프로토콜 2에서는 새 옵코드가 long의 선형 시간 피클링과 언피클링을 지원합니다.

bool 피클링

프로토콜 2는 TrueFalse를 직접 피클링하기 위한 새로운 연산 코드를 도입합니다. 프로토콜 0과 1에서는, 언피클러가 불리언이 의도되었음을 인식할 수 있도록 피클 내 정수 표현에 트릭을 사용하여 불리언을 정수로 피클링합니다. 이 트릭은 피클링된 불리언 하나당 4바이트를 소비했습니다. 새로운 불리언 연산 코드는 불리언 하나당 1바이트를 소비합니다.

작은 튜플 피클링

프로토콜 2는 길이가 1, 2, 3인 튜플을 더 압축적으로 피클링하기 위한 새로운 연산 코드를 도입합니다. 프로토콜 1은 이전에 빈 튜플을 더 압축적으로 피클링하기 위한 연산 코드를 도입한 바 있습니다.

프로토콜 식별

프로토콜 2는 모든 프로토콜 2 피클이 시작할 때 사용되는 새로운 연산 코드를 도입하여, 해당 피클이 프로토콜 2임을 식별합니다. 따라서 이전 버전의 파이썬에서 프로토콜 2 피클을 언피클링하려고 시도하면 즉시 “알 수 없는 연산 코드” 예외가 발생합니다.

큰 리스트와 딕셔너리의 피클링

프로토콜 1은 큰 리스트와 딕셔너리를 “한 덩어리로” 피클링하여 피클 크기를 최소화하지만, 언피클링 시 언피클링 대상 객체만큼 큰 임시 객체를 생성해야 합니다. 프로토콜 2 변경 사항의 일부는 큰 리스트와 딕셔너리를 각각 최대 1000개 요소로 이루어진 조각들로 나누어, 언피클링 시 1000개 요소를 담는 데 필요한 것보다 더 큰 임시 객체를 생성할 필요가 없도록 합니다. 다만 이는 프로토콜 2의 일부가 아니며, 생성되는 연산 코드는 여전히 프로토콜 1의 일부입니다. 선택적으로 새로운 listitems나 dictitems 이터레이터를 반환하는 __reduce__ 구현도 이 언피클링 임시 공간 최적화의 혜택을 받습니다.