[Final] PEP 307 - Extensions to the pickle protocol
원문 링크: PEP 307 - Extensions to the pickle protocol
상태: Final | 유형: Standards Track | 작성일: 31-Jan-2003
PEP 307 – pickle 프로토콜 확장
개요
이 PEP는 Python 2.3에서 도입된 새로운 pickle 프로토콜을 설명하며, 기존 new-style 객체들의 pickle 처리 문제를 해결하고 pickle 크기를 최적화합니다. 주로 API 변경 사항에 중점을 두지만, 일부 바이트 스트림 형식 세부 사항도 다룹니다. pickle 바이트 스트림 형식은 pickletools.py 모듈에 공식적으로 문서화되어 있습니다.
동기
Python 2.2에서 new-style 객체를 pickle하는 것은 비효율적이었고, classic 클래스 인스턴스에 비해 pickle 크기가 과도하게 커지는 문제가 있었습니다. 예를 들어, 간단한 new-style 객체의 pickle 크기는 classic 객체보다 훨씬 컸습니다 (33바이트 vs 86바이트). 이러한 비대화는 new-style 객체가 pickle 가능하려면 __reduce__를 사용해야 했기 때문입니다. 새로운 프로토콜(프로토콜 2)을 도입함으로써, 이 문제는 해결되어 pickle 크기가 크게 줄어듭니다 (위 예시에서 35바이트).
프로토콜 버전
기존 pickle 프로토콜은 텍스트 모드와 바이너리 모드로 구분되었습니다. 이제 텍스트 모드는 프로토콜 0, 바이너리 모드는 프로토콜 1로 명명됩니다. 이 PEP에서 새로 소개하는 프로토콜은 프로토콜 2입니다. pickle 프로토콜의 전통에 따라 프로토콜 2는 프로토콜 1의 상위 집합입니다.
- 프로토콜 식별: 프로토콜 2
pickle은 시작 부분에 해당 프로토콜 버전을 나타내는 새로운opcode를 포함합니다. 이는 이전 Python 버전에서 프로토콜 2pickle을unpickle하려고 할 경우 즉시 "알 수 없는opcode" 예외를 발생시킵니다. protocol인자:pickle관련 함수, 메서드, 생성자에서 바이너리 모드를 나타내던bin인자는protocol로 이름이 변경되었고, 이제 프로토콜 번호를 받습니다 (기본값은 0).- 최고 프로토콜 버전:
-1을protocol인자로 전달하면 해당 구현이 지원하는 가장 높은 프로토콜 버전을 선택할 수 있습니다.pickle및cPickle모듈은HIGHEST_PROTOCOL상수를 제공하여 모듈이 읽을 수 있는 가장 높은 프로토콜 번호를 확인할 수 있습니다. bin키워드 인자 경고:bin을 키워드 인자로 전달하는 방식은PendingDeprecationWarning이 발생하며 Python 2.4에서는DeprecationWarning으로 격상될 예정입니다.
보안 문제
이전 Python 버전에서는 unpickle 시 __safe_for_unpickling__ 속성이 1로 설정되었거나 copy_reg.safe_constructors에 등록된 함수만 호출하도록 하는 "안전 검사" 기능이 있었습니다. 그러나 이 기능은 잘못된 보안 의식을 줄 수 있으며, 실제로 Python 2.2 pickle.py 모듈의 버그로 인해 우회될 수 있었습니다.
Python 2.3부터는 이러한 모든 안전 검사가 공식적으로 제거되었고, 다음과 같은 경고로 대체됩니다.
경고: 신뢰할 수 없거나 인증되지 않은 소스에서 받은 데이터를 unpickle하지 마십시오.
이 경고는 안전 검사가 존재하는 이전 Python 버전에도 동일하게 적용됩니다.
확장된 __reduce__ API
클래스가 pickle을 제어하기 위해 사용할 수 있는 여러 API가 있습니다. 가장 널리 사용되는 것은 __getstate__와 __setstate__이지만, 가장 강력한 것은 __reduce__입니다.
__reduce__ 기능은 클래스에서 __reduce__ 메서드나 __reduce_ex__ 메서드를 구현하거나, copy_reg에 reduce 함수를 선언하여 제공할 수 있습니다. 반환 값은 동일하게 해석됩니다.
중요: classic 클래스 인스턴스의 pickle은 __reduce__ 또는 __reduce_ex__ 메서드를 찾지 않으므로, classic 클래스는 __getinitargs__ 및/또는 __getstate__를 사용하여 pickle을 사용자 정의해야 합니다.
__reduce__는 문자열 또는 튜플을 반환해야 합니다.
- 문자열 반환: 객체의 상태를
pickle하지 않고, 이름으로 참조되는 동등한 객체의 참조를 반환합니다. 반환되는 문자열은 객체의 로컬 이름이어야 합니다. - 튜플 반환: 길이가 2에서 5까지의 가변 크기 튜플입니다. 처음 두 항목(함수 및 인자)은 필수이며, 나머지 항목은 선택 사항입니다 (새로운 두 항목 포함).
function(필수): 객체의 초기 버전을 생성하기 위해 호출되는callable객체입니다. 나중에state를 추가하여pickle된 상태를 완전히 재구성할 수 있습니다. 이 함수는 그 자체로pickle가능해야 합니다.arguments(필수):function에 대한 인자 목록을 제공하는 튜플입니다.None일 수도 있습니다 (Zope 2의ExtensionClass를 위해 설계되었으나deprecated).state(선택 사항,new in this PEP): 추가 상태입니다.None이 아니면state가pickle되고,unpickle시obj.__setstate__(state)가 호출됩니다.__setstate__가 정의되지 않으면 기본 구현은state가 인스턴스 변수 이름을 값에 매핑하는 사전이라고 가정합니다.listitems(선택 사항,new in this PEP):None이 아니면 연속적인 리스트 항목을 생성하는iterator여야 합니다. 이 항목들은pickle되고obj.append(item)또는obj.extend(list_of_items)를 사용하여 객체에 추가됩니다. 주로 리스트 서브클래스에 사용됩니다.dictitems(선택 사항,new in this PEP):None이 아니면(key, value)형태의 튜플로 구성된 연속적인 사전 항목을 생성하는iterator여야 합니다. 이 항목들은pickle되고obj[key] = value를 사용하여 객체에 저장됩니다. 주로 딕셔너리 서브클래스에 사용됩니다.
참고: Python 2.3부터 __reduce__가 None 값을 가진 state를 반환할 때 unpickle 시 __setstate__는 호출되지 않습니다.
__reduce__ 구현이 Python 2.2와 2.3 모두에서 작동해야 하는 경우, pickle.format_version 변수를 확인하여 listitems 및 dictitems 기능 지원 여부를 결정할 수 있습니다. 이 값이 >= "2.0"이면 지원됩니다.
__reduce_ex__ API
__reduce__를 구현할 때 프로토콜 버전을 아는 것이 유용할 때가 있습니다. 이를 위해 __reduce__ 대신 __reduce_ex__ 메서드를 구현할 수 있습니다. __reduce_ex__는 존재할 경우 __reduce__보다 우선적으로 호출되며, 단일 정수 인자로 프로토콜 버전을 받습니다.
object 클래스는 __reduce__와 __reduce_ex__를 모두 구현합니다. 하지만 서브클래스가 __reduce__만 오버라이드하고 __reduce_ex__는 오버라이드하지 않으면, __reduce_ex__ 구현이 이를 감지하고 __reduce__를 호출합니다.
__reduce__ 구현이 없는 경우 pickle 사용자 정의
특정 클래스에 __reduce__ 구현이 없는 경우, 세 가지 경우가 다르게 처리됩니다.
-
classic클래스 인스턴스, 모든 프로토콜:__reduce__는 사용되지 않습니다. 대신__getstate__,__setstate__,__getinitargs__메서드를 통해pickle을 사용자 정의할 수 있습니다.__getstate__: 객체 자체를 참조하지 않고 객체의 상태를 나타내는pickle가능한 값을 반환해야 합니다. 기본 구현은self.__dict__를 반환합니다.__setstate__:__getstate__(또는 기본 구현)에 의해 반환된 값으로 호출됩니다. 기본 구현은state가 인스턴스 변수 이름을 값에 매핑하는 사전이라고 가정합니다.__getinitargs__:__setstate__를 호출하기 전에 새 객체를 생성해야 합니다.__getinitargs__메서드가 있을 경우, 이 메서드가 반환하는 튜플을 인자 목록으로 사용하여 클래스 생성자를 호출하여 인스턴스가 생성됩니다.
-
new-style클래스 인스턴스, 프로토콜 0 및 1:- Python 2.2와 동일하게 변경되지 않았습니다.
object에서 상속받은 기본__reduce__구현이 사용됩니다. 이 기본__reduce__는copy_reg._reconstructor를callable로,(D, B, basestate)를 인자로 사용합니다.__getstate__및/또는__setstate__메서드를 정의하여 사용자 정의할 수 있습니다.__getstate__가None과 같이false로 간주되는 값을 반환하면state는pickle되지 않고__setstate__는 호출되지 않습니다.slots가 있는new-style클래스 인스턴스는__getstate__메서드가 없으면 프로토콜 0 및 1로pickle될 수 없습니다.__getinitargs__는 무시됩니다.
-
new-style클래스 인스턴스, 프로토콜 2:object에서 상속받은 기본__reduce__구현은 무시됩니다. 대신 다른 기본 구현이 사용되어 프로토콜 0 또는 1보다new-style클래스 인스턴스를 더 효율적으로pickle할 수 있습니다.__getstate__,__setstate__,__getnewargs__세 가지 특수 메서드를 사용하여 사용자 정의합니다. (__getinitargs__는 다시 무시됩니다.)__getstate__:classic클래스와는 미묘한 차이가 있습니다.new-style클래스의__getstate__가None을 반환하면unpickle시__setstate__가 전혀 호출되지 않습니다.__setstate__:__getstate__가 반환하는 값 또는 위에서 설명한 기본state로 호출됩니다.__getnewargs__:__setstate__를 호출하기 전에 새 객체를 생성해야 합니다. 프로토콜 2에서는C.__new__(C, *args)와 같이 새 객체가 생성되도록 하는 새로운pickleopcode가 사용됩니다.args는__getnewargs__메서드가 정의된 경우 반환되는 튜플입니다.
__newobj__ unpickling 함수
__reduce__가 반환하는 unpickling 함수(튜플의 첫 번째 항목)의 이름이 __newobj__인 경우, pickle 프로토콜 2에서는 특별한 처리가 이루어집니다. __newobj__라는 이름의 unpickling 함수는 cls.__new__(cls, *args)와 같은 의미를 갖는다고 가정합니다.
pickle 프로토콜 2는 이 이름의 unpickling 함수를 특별히 처리하여, __newobj__에 대한 참조를 pickle하지 않고 cls.__new__(cls, *args)를 반환하는 pickle opcode를 발행합니다. 이것이 프로토콜 2 pickle이 classic pickle보다 훨씬 작은 주된 이유입니다.
확장 레지스트리 (Extension Registry)
프로토콜 2는 pickle 크기를 줄이기 위한 새로운 메커니즘을 지원합니다. 클래스 인스턴스가 pickle될 때, 클래스의 전체 이름(모듈 이름, 패키지 이름, 클래스 이름 포함)이 pickle에 포함됩니다. 특히 많은 작은 pickle을 생성하는 애플리케이션의 경우, 이는 각 pickle에서 반복되어야 하는 많은 오버헤드를 유발합니다.
확장 레지스트리는 가장 자주 사용되는 이름을 작은 정수로 나타낼 수 있게 하여 매우 효율적으로 pickle됩니다. 1-255 범위의 확장 코드는 opcode를 포함하여 2바이트만 필요하며, 256-65535 범위는 opcode를 포함하여 3바이트만 필요합니다.
확장 레지스트리는 확장 코드와 이름 간의 매핑으로 정의됩니다. unpickle될 때 확장 코드는 객체를 생성하지만, 이 객체는 모듈 이름과 클래스(또는 함수) 이름으로 이름을 해석하여 얻습니다.
확장 코드 범위는 다음과 같이 제안됩니다.
0: 예약됨 (사용되지 않음)1-127: Python 표준 라이브러리용128-191: Zope용192-239: 서드파티용240-255: 사적 사용 (할당되지 않음)256-MAX(2^31 - 1): 향후 할당용
확장 레지스트리 API
확장 레지스트리는 copy_reg 모듈의 private 전역 변수로 관리됩니다. 레지스트리를 조작하기 위해 다음 세 가지 함수가 정의됩니다.
add_extension(module, name, code): 확장 코드를 등록합니다.module과name은 문자열이어야 하며,code는1부터MAX까지의 정수여야 합니다.remove_extension(module, name, code): 이전에 등록된(module, name)과code간의 매핑을 제거합니다.clear_extension_cache(): 확장 코드 구현이 자주 명명되는 객체를 로드하는 속도를 높이기 위해 캐시를 사용할 수 있습니다. 이 메서드를 호출하여 캐시를 비울 수 있습니다.
copy 모듈
전통적으로 copy 모듈은 copy() 및 deepcopy() 작업을 사용자 정의하기 위한 pickle API의 확장된 서브셋을 지원했습니다. Python 2.3에서는 copy 모듈에 여러 변경 사항이 적용됩니다.
__reduce_ex__가 지원됩니다 (항상 프로토콜 버전 2로 호출됨).__reduce__의 4개 및 5개 인자 반환 값이 지원됩니다.__reduce__메서드를 찾기 전에pickle과 마찬가지로copy_reg.dispatch_table을 참조합니다.object에서__reduce__메서드를 상속받은 경우,pickle프로토콜 2와 동일한 API(__getnewargs__,__getstate__,__setstate__)를 사용하여 더 나은 구현으로 (무조건) 대체됩니다.
이러한 변경으로 인해 Python 2.2에서 copy 가능했던 일부 new-style 클래스가 Python 2.3에서는 copy 불가능할 수 있습니다. 이는 __new__가 오버라이드되고 클래스 인자 외에 하나 이상의 필수 인자를 가질 때 발생합니다. 이를 해결하려면 적절한 인자 튜플을 반환하는 __getnewargs__ 메서드를 추가해야 합니다.
Python longs pickle링
프로토콜 0과 1에서는 Python longs(정수)를 pickle 및 unpickle하는 데 자릿수에 따라 2차 시간 복잡도가 소요되었습니다. 프로토콜 2에서는 새로운 opcode가 선형 시간 복잡도로 longs를 pickle 및 unpickle할 수 있도록 지원합니다.
bools pickle링
프로토콜 2는 True와 False를 직접 pickle하기 위한 새로운 opcode를 도입합니다. 프로토콜 0과 1에서는 bool이 정수로 pickle되었고, 이는 bool당 4바이트를 소비했습니다. 새로운 bool opcode는 bool당 1바이트를 소비합니다.
작은 튜플 pickle링
프로토콜 2는 길이가 1, 2, 3인 튜플을 더 간결하게 pickle하기 위한 새로운 opcode를 도입합니다. 프로토콜 1에서는 빈 튜플을 더 간결하게 pickle하기 위한 opcode가 이전에 도입되었습니다.
대규모 리스트 및 딕셔너리 pickle링
프로토콜 1은 대규모 리스트와 딕셔너리를 "한 덩어리"로 pickle하여 pickle 크기를 최소화하지만, unpickle 시 unpickle되는 객체만큼 큰 임시 객체를 생성해야 했습니다. 프로토콜 2의 변경 사항 중 일부는 대규모 리스트와 딕셔너리를 최대 1000개의 요소로 구성된 조각으로 분할하여 unpickle 시 1000개의 요소를 담는 데 필요한 것보다 큰 임시 객체를 생성할 필요가 없도록 합니다.
This concludes the translation and summary of PEP 307.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.