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

Python 개선 제안 한국어 번역

PEP 307 – pickle 프로토콜 확장

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의 새 pickle 프로토콜을 설명합니다.

새 pickle 프로토콜을 정하려면 두 가지를 정의해야 합니다. 하나는 피클된 데이터를 이루는 바이트 스트림이고, 다른 하나는 객체와 피클링 및 언피클링 엔진 사이의 인터페이스입니다. 이 PEP는 주로 API 문제를 다루지만, 어떤 선택을 한 이유를 설명하기 위해 바이트 스트림 형식의 세부 사항을 다루기도 합니다. pickle 바이트 스트림 형식은 표준 라이브러리 모듈 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__를 사용해야 한다는 점입니다. 충분히 검토한 끝에, 새 스타일 객체의 피클 크기를 줄이는 유일한 방법은 pickle 프로토콜에 새 옵코드를 추가하는 것이라는 결론을 내렸습니다. 결과적으로 새 프로토콜에서는 위 예제의 피클 크기가 35가 됩니다(프로토콜 버전을 나타내려고 시작 부분에 2바이트를 더 사용하지만, 이것이 꼭 필요한 것은 아닙니다).

프로토콜 버전

이전에는 피클링할 때 텍스트 모드와 이진 모드를 구분했습니다(언피클링할 때는 구분하지 않았습니다). 설계상 이진 모드는 텍스트 모드의 상위 집합입니다. 그래서 언피클러는 들어오는 피클이 텍스트 모드인지 이진 모드인지 미리 알 필요가 없습니다. 언피클링에 쓰는 가상 머신은 모드와 관계없이 같으며, 텍스트 모드에서는 일부 옵코드가 쓰이지 않을 뿐입니다.

이제는 소급하여 텍스트 모드를 프로토콜 0, 이진 모드를 프로토콜 1이라고 부릅니다. 새 프로토콜은 프로토콜 2라고 부릅니다. 피클링 프로토콜의 관례에 따라 프로토콜 2는 프로토콜 1의 상위 집합입니다. 다만 앞으로의 피클링 프로토콜이 가장 오래된 프로토콜의 상위 집합이 아니어도 되도록, 프로토콜 2 피클의 시작 부분에 새 옵코드를 넣어 프로토콜 2를 사용한다는 것을 나타냅니다. 지금까지 Python의 각 릴리스는 이전의 모든 릴리스가 작성한 피클을 읽을 수 있었습니다. 물론 프로토콜 N으로 작성한 피클은 프로토콜 N을 도입한 버전보다 이전의 Python에서는 읽을 수 없습니다.

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

마침 이전 Python 버전에서는 ‘bin’ 인자에 2를 전달해도 1을 전달했을 때와 결과가 같았습니다. 그렇지만 여기서는 특별한 경우를 하나 추가합니다. 음수를 전달하면 해당 구현이 지원하는 가장 높은 프로토콜 버전이 선택됩니다. 이 방식은 이전 Python 버전에서도 동작합니다. 따라서 하위 호환성과 상위 호환성을 모두 지키면서 사용할 수 있는 가장 높은 프로토콜을 선택하는 데 쓸 수 있습니다. 또한 pickle와 cPickle 모두 새 모듈 상수 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__ 구현은 pickle.format_version 변수를 확인하여 listitems와 dictitems 기능을 사용할지 결정할 수 있습니다. 이 값이 >= "2.0"이면 이 기능들이 지원됩니다. 그렇지 않다면 리스트나 딕셔너리 항목은 어떤 방식으로든 ‘state’ 반환값에 포함되어야 하며, __setstate__ 메서드는 상태의 일부로서 리스트나 딕셔너리 항목을 받아들일 수 있도록 준비되어 있어야 합니다(이를 어떻게 처리할지는 애플리케이션에 달려 있습니다).

__reduce_ex__ API

__reduce__를 구현할 때 프로토콜 버전을 알면 유용한 경우가 있습니다. 이는 __reduce__ 대신 __reduce_ex__라는 이름의 메서드를 구현하여 할 수 있습니다. __reduce_ex__가 존재하면 __reduce__보다 우선하여 호출됩니다(하위 호환성을 위해 __reduce__를 계속 제공해도 됩니다). __reduce_ex__ 메서드는 프로토콜 버전을 나타내는 정수 인자 하나와 함께 호출됩니다.

객체 클래스 ‘object’는 __reduce__와 __reduce_ex__를 모두 구현합니다. 그러나 서브클래스가 __reduce_ex__는 재정의하지 않고 __reduce__만 재정의하면, __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__ 대입으로 그 인스턴스의 클래스를 원하는 클래스로 바꿉니다. 이렇게 하면 __dict__가 비어 있고 __init__이 호출되지 않은, 원하는 클래스의 인스턴스가 만들어집니다.
  • 그러나 클래스에 __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 함수 객체, 내장 함수 및 메서드 객체, 새 스타일 타입 객체(== 새 스타일 클래스 객체). 이들은 값이 아니라 이름으로 피클링됩니다. 언피클링할 때 같은 이름(완전한 모듈 이름과 그 모듈 안의 변수 이름을 합친 것)을 가진 객체에 대한 참조로 대체됩니다.

기본 __reduce__ 구현은 위에서 언급하지 않은 내장 타입과 C로 구현된 새 스타일 클래스에 대해 피클링 시점에 실패합니다. 이들이 피클링될 수 있으려면 프로토콜 0과 1에서 사용자 정의 __reduce__ 구현을 제공해야 합니다.

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

피클링할 객체의 클래스를 D라고 합시다. 먼저 C로 구현된(내장 타입이든 확장 클래스가 정의한 타입이든) 가장 가까운 베이스 클래스를 찾습니다. 이 베이스 클래스를 B라 하고, 피클링할 객체의 클래스를 D라 합니다. B가 객체 클래스 ‘object’가 아니라면, 클래스 B의 인스턴스는 (위의 세 항목에 정의된 대로) 내장 지원을 갖추거나 기본이 아닌 __reduce__ 구현을 가짐으로써 피클 가능해야 합니다. B는 D와 같은 클래스여서는 안 됩니다(만약 같다면 D가 Python으로 구현되지 않았다는 뜻이 됩니다).

기본 __reduce__가 만들어 내는 호출 가능 객체는 copy_reg._reconstructor이고, 그 인자 튜플은 (D, B, basestate)입니다. 여기서 basestate는 B가 내장 object 클래스이면 None이고, 그렇지 않으면 basestate는 다음과 같습니다.

basestate = B(obj)

B가 내장 객체 클래스 object가 아닌 경우입니다. 이는 내장 타입의 서브클래스를 피클링하는 데 맞춘 것입니다. 예를 들어 list(some_list_subclass_instance)는 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에서는 다음과 같이 새 객체를 생성하게 하는 새로운 피클링 옵코드가 사용됩니다:

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’가 주어졌을 때 __newobj__에 대한 참조는 함께 피클링하지 않으면서 cls.__new__(cls, *args)를 반환하는 피클링 옵코드를 내보냅니다(이는 __reduce__ 구현이 없을 때 프로토콜 2가 새 스타일 클래스 인스턴스에 사용하는 것과 같은 피클링 옵코드입니다). 이것이 프로토콜 2 피클이 클래식 피클보다 훨씬 작은 주된 이유입니다. 물론 피클링 코드는 __newobj__라는 이름의 함수가 실제로 기대한 의미를 가지는지 검증할 수 없습니다. __newobj__라는 이름으로 다른 것을 반환하는 언피클링 함수를 사용한다면, 그 결과는 스스로 감수해야 합니다.

이 기능은 Python 2.2에서도 안전하게 사용할 수 있습니다. __newobj__의 권장 구현에는 Python 2.3에 의존하는 부분이 전혀 없습니다.

확장 레지스트리

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

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

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

피클 프로토콜의 설계 목표 중 하나는 피클을 “컨텍스트 독립적”으로 만드는 것입니다. 즉, 피클이 참조하는 클래스를 담은 모듈이 설치되어 있기만 하면, 그 클래스들을 미리 임포트할 필요 없이 피클을 언피클링할 수 있습니다.

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

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

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

둘째, 일부 대규모 Python 프로젝트(예: Zope)에는 “사적 용도” 범위 밖의 확장 코드 범위를 할당할 수 있으며, 해당 프로젝트는 그 범위를 적절하다고 판단하는 대로 배정할 수 있습니다.

확장 레지스트리는 확장 코드와 이름 사이의 매핑으로 정의됩니다. 확장 코드가 언피클링되면 최종적으로 객체가 만들어지는데, 이 객체는 이름을 모듈 이름과 그 뒤에 오는 클래스(또는 함수) 이름으로 해석하여 얻습니다. 이름에서 객체로의 매핑은 캐시됩니다. 어떤 이름은 임포트할 수 없을 가능성도 충분히 있습니다. 그러나 그러한 이름에 대한 참조를 담은 피클을 언피클링해야 하는 일이 없는 한 문제가 되지 않을 것입니다. (프로토콜 0이나 1을 사용하는 피클에서 그러한 이름을 직접 참조하는 경우에도 같은 문제가 이미 존재합니다.)

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

처음 마지막 개수 용도
0 0 1 예약됨 — 절대 사용되지 않음
1 127 127 Python 표준 라이브러리용으로 예약됨
128 191 64 Zope용으로 예약됨
192 239 48 서드 파티용으로 예약됨
240 255 16 사적 용도로 예약됨 (절대 할당되지 않음)
256 MAX MAX 향후 할당을 위해 예약됨

MAX는 2147483647, 즉 2**31-1을 나타냅니다. 이는 현재 정의된 프로토콜의 엄격한 제약입니다.

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

확장 레지스트리 API

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

add_extension(module, name, code)
확장 코드를 등록합니다. 모듈을 나타내는 module 인자와 name 인자는 문자열이어야 하며, 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__를 사용하고 list와 dict 서브클래스 및 슬롯을 처리하는 더 나은 메서드로 대체됩니다.

마지막 변경의 결과로, Python 2.2에서 복사 가능했던 일부 새 스타일 클래스는 Python 2.3에서 복사할 수 없습니다. (이러한 클래스는 pickle 프로토콜 2로도 피클링할 수 없습니다.) 이러한 클래스의 최소 예시는 다음과 같습니다:

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

이 문제는 __new__가 오버라이드되어 있고 클래스 인자 외에 필수 인자를 하나 이상 가질 때만 발생합니다.

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

Python long 피클링

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

bool 피클링

프로토콜 2는 True와 False를 직접 피클링하기 위한 새 옵코드를 도입합니다. 프로토콜 0과 1에서는 bool을 정수로 피클링하며, 언피클러가 bool을 의도한 것임을 인식할 수 있도록 피클 안의 정수 표현에 요령을 사용합니다. 그 요령은 피클링되는 bool 하나당 4바이트를 소비했습니다. 새 bool 옵코드는 bool 하나당 1바이트를 소비합니다.

작은 튜플의 피클링

프로토콜 2는 길이가 1, 2, 3인 튜플을 더 간결하게 피클링하기 위한 새 옵코드를 도입합니다. 앞서 프로토콜 1은 빈 튜플을 더 간결하게 피클링하기 위한 옵코드를 도입했습니다.

프로토콜 식별

프로토콜 2는 해당 피클이 프로토콜 2임을 식별하는 새 옵코드를 도입하며, 모든 프로토콜 2 피클은 이 옵코드로 시작합니다. 따라서 이전 버전의 Python에서 프로토콜 2 피클을 언피클하려고 하면 즉시 “unknown opcode”(알 수 없는 옵코드) 예외가 발생합니다.

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

프로토콜 1은 큰 리스트와 딕셔너리를 “한 덩어리로” 피클링합니다. 이렇게 하면 피클 크기는 최소화되지만, 언피클링할 때 언피클링되는 객체만큼 큰 임시 객체를 만들어야 합니다. 프로토콜 2 변경 사항 중 일부는 큰 리스트와 딕셔너리를 각각 1000개 이하의 요소로 이루어진 조각으로 나누어, 언피클링할 때 1000개의 요소를 담는 데 필요한 것보다 큰 임시 객체를 만들 필요가 없게 합니다. 그러나 이것은 프로토콜 2의 일부가 아닙니다. 생성되는 옵코드는 여전히 프로토콜 1의 일부입니다. 선택 사항인 새 listitems 또는 dictitems 이터레이터를 반환하는 __reduce__ 구현도 이 언피클링 임시 공간 최적화의 이점을 누립니다.