pickle — Python 객체 직렬화

pickle — Python 객체 직렬화

소스 코드: Lib/pickle.py

pickle 모듈은 Python 객체 구조를 직렬화하고 역직렬화하기 위한 바이너리 프로토콜을 구현해요. "피클링(pickling)"은 Python 객체 계층 구조를 바이트 스트림으로 변환하는 과정이고, "언피클링(unpickling)"은 반대 연산으로, 바이트 스트림을 객체 계층 구조로 다시 변환하는 거예요. 피클링(그리고 언피클링)은 대안적으로 "직렬화", "마샬링" 또는 "평탄화"라고도 불리는데, 한 객체가 나중에 같은 상태를 가진 객체로 재구성될 수 있도록 데이터를 바이트 스트림 형태로 전송·저장하는 방법이기 때문이에요.

경고pickle 모듈은 신뢰할 수 없는 데이터에 대해 안전하지 않아요. 언피클링 데이터는 기본적으로 피클링 과정 중에 임의의 코드 실행을 초래할 수 있어요. 신뢰할 수 없는 출처에서 온 데이터를 언피클링하지 마세요. 또는 서명 검증과 같은 더 강력한 인증 절차가 없으면 신뢰할 수 없는 소스의 데이터를 언피클링하지 마세요. pickle로 만든 데이터를 로드하는 것도 그 데이터를 만든 픽클러(피클러)를 실행하는 것과 같은 코드 승인을 의미한다는 점을 기억하세요. 주의 깊게 수용된 입력 데이터만 피클링하세요. 픽클 데이터가 안전한 이유와 제한 사항에 대한 더 자세한 정보는 pickle 모듈에 대한 질문과 답변을 참고하세요.

출처: Python 표준 라이브러리

본문

파일 인터페이스와의 관계 (Relationship to other Python modules)

picklemarshal과 함께 Python 포맷 변경과 독립적으로, 객체를 나타내는 가장 기본적인 방식이에요. marshal 모듈은 기본적으로 .pyc 파일의 저장에 사용돼요. pickle은 모듈과 클래스에 대한 정보를 저장하고 marshal보다 훨씬 더 많은 객체 유형을 처리할 수 있어요.

json은 표준 라이브러리의 또 다른 직렬화 모듈이에요. json은 특정 기본 유형만 처리하며, 사용자 정의 클래스와 같은 것을 지원하지 않아요. json은 사람이 읽을 수 있는 형식을 생성해요. pickle은 이진 형식을 생성해요.

데이터 스트림 형식 (Data stream format)

피클 데이터 형식은 pickle 모듈이 사용하는 데 충분한 정보를 포함해 객체를 다시 생성해요. 피클은 재귀 데이터 구조를 완벽하게 지원해요. 객체는 한 번만 피클링되고 같은 피클은 스트림의 해당 인스턴스에 대한 참조로 대체되므로 같은 재귀 참조를 여러 번 역참조(memo)할 수 있어요. 같은 데이터를 공유하는 재귀 객체는 단순히 공유된 것으로 처리될 수 있는 것이 아니라 피클링될 수 있어요.

피클 데이터 스트림에는 구조(구조체 구조를 나타내는 opcode를 포함해 미리 정의된 기본 표현)와 Python 기본값에 독립적인 유형 정보가 결합돼 있어요. 그러므로 프로토콜이 열거 opcode뿐만 아니라 클래스 표현 범위 내에서 작성되는 한, 원본 Python 객체를 재구성하는 데 필요한 정보(예: __module__, __qualname__, __reduce_ex__ 등)는 피클 데이터 스트림에 포함돼요.

데이터 스트림은 중립적인 바이트 포맷으로 쓰여지기 때문에, 독립적으로 개발된 Python 프로그램들이 피클 프로토콜을 사용해 서로 데이터를 주고받을 수 있어요. 프로토콜 2부터는 명시적 프로토콜이 파이썬 버전과 저장되는 피클 스트림을 함께 표시할 수 있어요.

프로토콜의 버전 번호와 해석은 확장 가능성이 없어요. 데이터 스트림이 Python 버전에 영향을 받을 수 있고, 관련 프로토콜을 사용해 저장된 데이터도 파이썬 언어 정의에 독립성이 있다는 점을 잊지 마세요.

프로토콜 버전 (Protocol versions)

pickle 프로토콜은 여러 버전이 존재하며, 각 프로토콜이 이전 버전보다 넓은 기능 집합을 가지게 확장됐어요.

  • 프로토콜 버전 0 — 원래의 "인간이 읽을 수 있는" 프로토콀로, 이전 버전과의 호환성을 위해 이전 버전의 하위 집합이에요.
  • 프로토콜 버전 1 — 원래의 이진 형식.
  • 프로토콜 버전 2 — Python 2.3에서 도입됨. 새 클래스에 대한 보다 효율적인 피클링을 제공.
  • 프로토콜 버전 3 — Python 3.0에서 도입됨. bytes 객체에 대한 명시적 지원이 있고, bytearray에 대한 Python 2.x 언피클링 지원이 제거됐어요. Python 2에서 언피클링할 필요가 없는 사람들의 기본 프로토콜이에요.
  • 프로토콜 버전 4 — Python 3.4에서 도입됨. 매우 큰 객체, 더 많은 객체 유형에 대한 지원, 몇 가지 최적화와 함께 추가.

pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None)

피클된 표현 obj를 열린 파일 객체 file에 써요. filePickler에 바이트를 쓸 수 있도록 바이트를 받는 write() 메서드를 가져야 해요.

이것은 Pickler(file, protocol).dump(obj)와 같아요. 이 메서드를 직접 사용하려면 Pickler 생성자와 dump() 메서드의 인자를 참고하세요.

선택적 protocol 인자는 피클러와 언피클러가 사용할 프로토콜 버전을 알려줘요. protocol에 대해 다음 값을 정의할 수 있어요:

  • None — 사용할 기본 프로토콜이 선택됨. Python 3.8+에서 기본 프로토콜은 4입니다.
  • 0~HIGHEST_PROTOCOL — 사용자는 선택된 프로토콜을 사용할 수 있다.

피클을 저장하는 파일에 대한 규칙은 Pickler 설명을 참고하세요.

pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None)

피클된 표현 obj를 바이트 객체 bytes로 반환해요.

pickle.load(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

피클된 표현 obj를 열린 파일 객체 file에서 읽어요.

이것은 Unpickler(file).load()와 같아요.

피클 프로토콜 버전은 자동으로 감지되므로 protocol 인자는 필요하지 않아요. 피클된 표현이 지금보다 더 많은 메모리를 점유하는 객체가 되면 안 돼요.

기본적으로 피클 파일이 그 파일을 생성한 Python보다 오래된 Python 버전으로 생성된 것이면 상당히 많은 것이 자동으로 이루어져요. 좀 더 구체적으로는 인코딩과 오류 설정이 다음에 정확히 해당돼요.

pickle.loads(data, /, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

피클된 표현 obj를 바이트 객체 bytes에서 읽어, 다시 생성된 객체 계층 구조를 반환해요.

피클 프로토콜 버전은 자동으로 감지되므로 protocol 인자는 필요하지 않아요. 피클된 표현이 지금보다 더 많은 메모리를 점유하는 객체가 되면 안 돼요.

bytes 객체는 피클된 요소의 일부를 포함하지 않을 수 있고, 피클된 obj가 정확히 하나 이상이라고 가정하고 파싱하는 대신, 피클된 objbytes 객체가 정확히 들어 있다고 가정하고 파싱해요. 이것은 bytes 뒤에 pickle.load() 호출을 반복할 때 특히 유용해요.

Pickler 클래스

class pickle.Pickler(file, protocol=None, *, fix_imports=True, buffer_callback=None)

이것은 바이너리 피클 파일을 작성하는 데 사용되는 낮은 수준의 인터페이스예요. Pickler의 인스턴스를 만들 때 file은 피클 데이터를 쓰는 데 사용되는 파일 객체나 유사한 객체여야 해요. filewrite() 메서드는 스트림의 바이트를 쓸 수 있어야 해요.

선택적 protocol 인자는 사용할 프로토콜을 지정해요. 프로토콜 버전의 기본값은 None으로, 기본 프로토콜(4)을 사용하게 돼요.

Pickler는 바이너리 파일 객체를 쓰기 위한 것이고, 텍스트 파일 객체는 쓰지 않는다는 점에 주의하세요(write()에 전달된 데이터가 bytes로 쓰여지므로). 텍스트 모드로 열린 파일에 쓰면 TypeError가 발생해요.

Pickler 객체에는 다음 메서드가 있어요:

dump(obj)

obj의 피클된 표현을 Pickler가 만든 스트림에 써요.

persistent_id(obj)

기본적으로 아무것도 하지 않아요. 하위 클래스가 오버라이드할 수 있어요. persistent_id()None을 반환하면 obj는 평소대로 피클링돼요. 어떤 다른 값을 반환하면 Pickler가 표준 피클링 대신 persistent_id()의 반환 값을 영속 ID(persistent ID)로 사용해요. 이 값의 의미는 Unpickler.persistent_load()에 정의돼 있어요.

dispatch_table

Pickler 객체에는 재구성 가능한 디스패치 테이블을 가질 수 있는 dispatch_table 속성이 있어요. 이것은 copyreg.pickle()로 정의된 클래스별 피클링 함수를 저장하고 인터페이스하는 데 유용해요. copyreg 모듈은 dispatch_table을 사용하는 가장 간단한 인터페이스를 제공해요. 이 표가 비어 있지 않으면 피클링 중에 타입이 정의된 경우 아래에 정의된 대로 다른 피클링 동작을 우선시해요.

reducer_override(obj)

dispatch_table을 대체하고, 추가로 reduce() 및 유사한 기능을 사용자 정의할 수 있는 특수 reducer를 지정할 수 있어요. reduce()가 반환 값으로 NotImplemented를 제공하면 Pickler가 기본 동작으로 폴백해요. NotImplemented를 제공하지 않으면 obj는 정상적인 피클링이 아니라 이 reducer를 사용해 피클링돼요.

fast

사용되지 않는 기능이에요. fast가 참이면 피클링이 메모 순환 참조를 만들지 않아 빠른 피클링을 할 수 있게 해 줘요.

Unpickler 클래스

class pickle.Unpickler(file, *, fix_imports=True, encoding='ASCII', errors='strict', buffers=None)

이것은 바이너리 피클 파일을 읽는 데 사용되는 낮은 수준의 인터페이스예요. Unpickler의 인스턴스를 만들 때 file은 바이너리 피클 데이터를 읽는 데 사용되는 파일 객체나 유사한 객체여야 해요.

Unpickler 또는 load()에 전달되는 file이 이미 buffer 객체로 피클링된 객체를 만드는데 기여할 수 있다면 buffers를 사용해 다른 Python 버전이 전달한 버퍼 리스트를 전달할 수 있어요.

Unpickler 객체에는 다음 메서드가 있어요:

load()

피클 스트림에서 Unpickler가 만든 객체의 표현을 읽어 돌려줘요.

persistent_load(pid)

기본적으로 UnpicklerError를 발생시켜요. 하위 클래스가 오버라이드할 수 있어요. persistent_load()가 호출되면 persistent_id()가 반환한 값이 될 pid 인자 하나를 받아요. 어떤 객체를 반환할지는 피클링 시 persistent_id()의 값에 따라 결정돼요.

find_class(module, name)

필요하면 module을 임포트하고 name이라는 객체를 반환해요. modulename 인자는 str 객체여야 해요. 그렇지 않으면 TypeError가 발생해요. 기본적으로 find_class()는 특정할 필요 없이 클래스의 정규화된 이름을 사용해 검색한다는 점에 주의하세요.

무엇을 피클링/언피클링할까 (What can be pickled and unpickled?)

다음 유형을 피클링할 수 있어요:

  • None, True, False
  • 정수, 부동소수점 숫자, 복소수
  • 문자열, 바이트, 바이트 배열
  • 피클링 가능한 객체를 포함하는 튜플, 리스트, 세트, 사전
  • 모듈 수준에서 정의된 함수(컨텍스트 핸들러가 아닌, 표준 def로 정의된 함수)
  • 모듈 수준에서 정의된 내장 함수
  • 모듈 수준에서 정의된 클래스
  • 이런 객체의 __dict__ 또는 __setstate__()를 호출한 결과에 의해 상태가 있는 클래스의 인스턴스

함수와 클래스는 정의된 모듈에서 가져올 수 있어야 하고, 함수는 모듈 수준에서 정의되거나 __main__에서 정의돼야 하며, 클래스는 모듈 수준에서 정의돼야 해요.

일반적으로 함수가 모듈 수준에서 정의될 수 없는 경우에도 피클링할 수 없어요. 클로저, 람다, 생성기 함수에 대한 지름길은 없어요.

피클링할 수 없는 유형은 다음과 같아요.

  • 모듈 수준에서 정의되지 않은 함수 및 클래스
  • 람다 함수
  • 중첩된 함수

피클링할 수 없는 객체는 인터프리터가 다른 버전의 Python에서 정의한 객체로, 피클링할 수 없어요.

예제 (Examples)

>>> import pickle
>>> data1 = {'a': [1, 2.0, 3, 4+6j],
...          'b': ('character string', b'byte string'),
...          'c': {None, True, False}}
>>> with open('data.pickle', 'wb') as f:
...     pickle.dump(data1, f, pickle.HIGHEST_PROTOCOL)
>>> import pickle
>>> with open('data.pickle', 'rb') as f:
...     data2 = pickle.load(f)

권장되지 않는 사용 (Unpickling untrusted data)

피클 데이터를 만들고 로드하는 것은 작성자의 실행에 악의적인 임의 입력을 주입하는 것이기를 요구하거나, Python에서 자동으로 실행되는 모든 것을 만드는 것과 같다고 생각될 수 있어요. 일반적인 가이드로, 신뢰할 수 없는 입력을 피클링, 언피클링, 또는 로드하지 마세요.

더 안전한 로딩 (Safer loading)

피클 데이터를 로드하는 것의 불변의 법칙은 가장 안전한 로딩이 로드하지 않는 것이라는 점을 이해해야 해요. 이 모듈이 제공하는 유일한 완전히 안전한 로딩 메커니즘은 없지만, 세 가지 일반적인 방법이 있어요.

  • 피클 데이터를 신뢰할 수 없거나 서명되지 않은 출처에서 로드하지 마세요.
  • 서명된 데이터를 로드할 때는 디지털 서명을 반드시 검증하세요.
  • 로드하는 데이터가 피클될 때 알려지거나 모르는 헤더를 포함하는지 확인하세요 — 피클 파일을 만들 때는 객체를 픽클링하면서 파일 헤더를 쓰세요.

더 알아보기

  • copyreg — 피클 등록 함수.

  • copy — 얕은 복사본과 깊은 복사본.

  • marshall — 원시 데이터를 기반으로 직렬화.

  • json — JSON 데이터.

  • pickle (원문)