pickle — 파이썬 객체 직렬화

pickle — 파이썬 객체 직렬화

pickle 모듈은 파이썬 객체 구조를 직렬화·역직렬화하는 바이너리 프로토콜을 구현해요. "피클링(pickling)"은 파이썬 객체 계층을 바이트 스트림으로 변환하는 과정이고, "언피클링(unpickling)"은 그 역연산으로 바이트 스트림(바이너리 파일 또는 bytes-like 객체에서)을 다시 객체 계층으로 되돌리는 것이에요. 이를 각각 직렬화·역직렬화라고도 불러요.

출처: Python documentation

본문

⚠️ 경고: pickle 모듈은 안전하지 않아요. 신뢰하는 데이터만 언피클하세요. 언피클링 중에 임의의 코드를 실행하는 악의적인 pickle 데이터를 만들 수 있어요. 신뢰할 수 없는 소스에서 온 데이터나 변조된 데이터는 절대 언피클하지 마세요. 변조를 막으려면 hmac 으로 데이터에 서명하는 것을 고려하세요. 신뢰할 수 없는 데이터를 처리한다면 json 같은 더 안전한 직렬화 형식이 더 적합할 수 있어요.

다른 파이썬 모듈과의 관계

marshal 과의 비교

파이썬에는 더 원시적인 직렬화 모듈 marshal 이 있지만, 일반적으로 파이썬 객체 직렬화에는 항상 pickle 을 선호해야 해요. marshal 은 주로 파이썬의 .pyc 파일을 지원하기 위해 존재해요.

  • marshal 은 사용자 정의 클래스와 그 인스턴스를 직렬화할 수 없지만, pickle 은 클래스 인스턴스를 투명하게 저장·복원해요(단, 클래스 정의는 가져올 수 있어야 하고 객체가 피클될 당시와 같은 모듈에 있어야 함).
  • marshal 의 직렬화 형식은 파이썬 버전 간 이식이 보장되지 않아요. 반면 pickle 의 형식은 호환 가능한 프로토콜을 고르면 릴리스 간 하위 호환이 보장돼요.

json 과의 비교

  • JSON 은 텍스트 직렬화 형식이고 pickle 은 바이너리 직렬화 형식이에요.
  • JSON 은 사람이 읽을 수 있지만 pickle 은 그렇지 않아요.
  • JSON 은 파이썬 생태계 밖에서도 널리 상호 운용되지만 pickle 은 파이썬 전용이에요.
  • JSON 은 기본적으로 내장 타입의 일부와 사용자 정의 클래스만 표현할 수 없지만, pickle 은 매우 많은 파이썬 타입을 표현할 수 있어요.
  • pickle 과 달리 신뢰할 수 없는 JSON 을 역직렬화해도 그 자체로 임의 코드 실행 취약점이 생기지 않아요.

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

pickle 이 사용하는 데이터 형식은 파이썬 전용이에요. 기본적으로 비교적 컴팩트한 바이너리 표현을 사용해요. pickletools 모듈이 pickle 이 생성한 데이터 스트림을 분석하는 도구를 포함해요.

현재 피클링에 쓸 수 있는 프로토콜은 6가지예요. 프로토콜이 높을수록 그 pickle 을 읽으려면 더 최신 파이썬이 필요해요.

  • 프로토콜 0 — 원래의 "사람이 읽을 수 있는" 프로토콜이에요.
  • 프로토콜 1 — 이전 버전 파이썬과도 호환되는 옛 바이너리 형식이에요.
  • 프로토콜 2 — 파이썬 2.3에 도입, new-style 클래스의 효율적인 피클링을 제공해요.
  • 프로토콜 3 — 파이썬 3.0에 추가. bytes 객체를 명시적으로 지원하며 파이썬 2.x 는 언피클할 수 없어요.
  • 프로토콜 4 — 파이썬 3.4에 추가. 매우 큰 객체, 더 많은 객체 종류 지원, 데이터 형식 최적화가 있어요.
  • 프로토콜 5 — 파이썬 3.8에 추가. 대역외 데이터(out-of-band)와 인밴드 데이터 가속을 지원해요.

참고: 직렬화는 영속화보다 더 원시적인 개념이에요. pickle 은 파일 객체를 읽고 쓰지만, 영속 객체의 이름 부여나 동시 접근 문제는 다루지 않아요. shelve 모듈이 DBM 스타일 데이터베이스 파일에서 객체를 피클·언피클하는 간단한 인터페이스를 제공해요.

모듈 인터페이스 (Module Interface)

객체 계층을 직렬화하려면 dumps() 함수를, 역직렬화하려면 loads() 함수를 호출해요. 더 많은 제어가 필요하면 Pickler 또는 Unpickler 객체를 만들 수 있어요.

상수:

  • pickle.HIGHEST_PROTOCOL — 사용 가능한 가장 높은 프로토콜 버전의 정수.
  • pickle.DEFAULT_PROTOCOL — 피클링에 사용되는 기본 프로토콜 버전의 정수. 현재 기본값은 5(파이썬 3.8 도입, 이전 버전과 호환되지 않음).

함수:

  • **pickle.dump(obj, file, protocol=None, *, fix_imports=True, buffer_callback=None) — 객체 obj 의 피클 표현을 연 파일 객체 file 에 써요. Pickler(file, protocol).dump(obj) 와 동등해요.
  • **pickle.dumps(obj, protocol=None, *, fix_imports=True, buffer_callback=None) — 파일에 쓰는 대신 객체 obj 의 피클 표현을 bytes 객체로 반환해요.

글로벌 제한 (Restricting Globals)

보안이 우려된다면 허용되는 전역을 제한하는 커스텀 Unpickler 를 만들 수 있어요. 예를 들어 find_class() 를 오버라이드해 builtins.eval 이나 os.system 같은 위험한 전역의 언피클을 금지할 수 있어요. 예제 출력에서 restricted_loadsos.system 이나 builtins.eval 을 포함한 데이터를 언피클하려 하면 UnpicklingError global 'os.system' is forbidden 를 발생시키는 걸 볼 수 있어요.

성능 (Performance)

pickle 프로토콜(프로토콜 2 이상)은 여러 일반 기능과 내장 타입의 효율적인 바이너리 인코딩을 갖추고 있어요. pickle 모듈은 C로 작성된 투명한 옵티마이저도 있어요.

예제 (Examples)

import pickle

# An arbitrary collection of objects supported by pickle.
data = {
    'a': [1, 2.0, 3+4j],
    'b': ("character string", b"byte string"),
    'c': {None, True, False}
}

with open('data.pickle', 'wb') as f:
    # Pickle the 'data' dictionary using the highest protocol available.
    pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)

저장된 pickle 데이터를 다시 읽으려면:

import pickle

with open('data.pickle', 'rb') as f:
    # The protocol version used is detected automatically, so we do not
    # have to specify it.
    data = pickle.load(f)

커맨드라인 인터페이스 (Command-line interface)

python -m pickle pickle_file [pickle_file ...]

pickle 파일의 내용을 표시해요. 신뢰할 수 없는 소스의 파일을 검사할 때는 -m pickletools 가 더 안전해요(pickle 바이트코드를 실행하지 않기 때문).

더 알아보기 (Learn more)