파이썬 pickle과 dill 직렬화 완전 가이드, 객체 그래프 저장부터 보안 위험까지 정리
⚠️ 신뢰할 수 없는 pickle 데이터를 절대 로드하면 안 되는 이유와 안전하게 커스텀 직렬화를 등록하는 방법까지 알려드립니다
파이썬을 쓰다 보면 “이 객체 그대로 저장했다가 나중에 다시 불러오면 안 돼?” 라는 고민을 한 번쯤 하게 됩니다.
데이터프레임, 모델, 함수, 사용자 정의 클래스 인스턴스까지 전부 메모리 상태 그대로 잠깐 얼려두고 싶은 순간이 있죠.
그때 사람들이 가장 많이 떠올리는 게 바로 pickle입니다.
한 줄로 직렬화하고 한 줄로 역직렬화할 수 있으니까요.
그런데 여기서 문제가 하나 생깁니다.
pickle은 정말 편리하지만, 동시에 보안적으로는 매우 위험한 방식이라서 “아무 데서 받은 pickle 파일을 그냥 열어본다”는 건 사실상 임의 코드 실행을 허용하는 거랑 비슷한 행동입니다.
이건 dill처럼 pickle을 확장한 라이브러리에도 그대로 해당돼요.
결국 pickle과 dill은 ‘파이썬 객체 그래프 전체를 직렬화한다’는 막강한 장점과 ‘검증되지 않은 소스에서는 절대 로드 금지’라는 치명적인 한계를 동시에 갖고 있는 도구라고 이해하는 게 맞습니다.
파이썬의 pickle 계열 직렬화는 내부적으로 프로토콜 버전이라는 걸 사용합니다.
버전이 올라갈수록 더 많은 타입을 효율적으로 저장할 수 있고, 덤프된 결과도 보통 더 작고 빠르게 로드됩니다.
또한 copyreg 모듈을 이용하면 내 커스텀 클래스가 어떻게 직렬화·역직렬화될지 직접 등록할 수도 있습니다.
이건 곧 “내가 만든 객체를 안정적으로 저장하고 복원하는 규칙”을 내 마음대로 정의할 수 있다는 뜻이라서, 장기 보관용 포맷을 설계할 때 굉장히 중요한 포인트가 됩니다.
이 글에서는 pickle과 dill이 실제로 어떤 식으로 파이썬 객체 그래프 전체를 저장하는지, 왜 신뢰할 수 없는 외부 소스의 데이터를 로드하면 안 되는지, 프로토콜 버전은 왜 신경 써야 하는지, 그리고 copyreg를 활용해서 커스텀 직렬화 규칙을 등록하는 방식까지 차근차근 정리합니다.
복잡한 얘기 같아도 차근히 보면 실무에서 바로 써먹을 수 있는 내용이라 생각보다 유용합니다.
📋 목차
📌 pickle과 dill 직렬화 개념과 특징
pickle은 파이썬 객체를 바이너리 형태로 직렬화하고, 다시 파이썬 객체로 복원하는 표준 라이브러리입니다.
즉, 메모리에 있던 상태를 그대로 파일로 얼렸다가 다시 해동하듯 복구하는 방식이라고 보면 이해가 편합니다.
리스트, 딕셔너리 같은 기본 타입뿐 아니라 사용자 정의 클래스 인스턴스, 심지어 중첩된 참조 관계까지 그대로 저장할 수 있다는 점이 핵심입니다.
이걸 흔히 객체 그래프 전체를 직렬화한다고 표현합니다.
단일 객체만 따로 떼어서 쓰는 게 아니라, 그 객체가 가리키는 다른 객체들까지 통째로 한 번에 저장한다는 의미입니다.
dill은 pickle과 비슷한 방식으로 작동하지만, 더 다양한 파이썬 오브젝트를 다룰 수 있도록 확장된 라이브러리로 알려져 있습니다.
예를 들어 일반 pickle로는 곤란한 람다 함수나 로컬 범위 안에서 정의된 함수 등까지도 저장하려고 할 때 dill을 쓰는 경우가 많습니다.
한마디로 dill은 “pickle이 되도록이면 못 하는 것도 해보겠다”에 가까운 쪽입니다.
그렇다고 해서 dill이 완전히 다른 규칙으로 움직이는 게 아니라, 기본적인 사용 방식은 거의 비슷합니다.
dump로 직렬화하고 load로 복원하는 식의 흐름은 같기 때문에 코드 레벨에서 갈아끼우기가 비교적 편합니다.
🧠 왜 ‘객체 그래프 직렬화’가 강력한가
일반적으로 데이터를 저장할 때는 JSON처럼 기본 타입만 기록하는 경우가 많습니다.
문자열, 숫자, 리스트 정도만 다루죠.
하지만 애플리케이션이 어느 정도 복잡해지면 단순한 딕셔너리만 저장한다고 끝나지 않습니다.
상태를 갖는 클래스 인스턴스, 내부적으로 다른 인스턴스를 참조하는 매니저 객체, 모델과 전처리기가 서로 연결된 머신러닝 파이프라인 같은 것들이 등장합니다.
pickle은 이런 연결 구조까지 한 번에 캡처합니다.
즉, “이 객체 안에 또 다른 객체가 있고, 그 안에 또 다른 상태가 있다”처럼 이어진 그래프 전체를 그대로 기록하려 합니다.
그래서 실험 환경 복원, 모델 학습 상태 체크포인트, 복잡한 캐시 등에서 pickle이 자주 등장합니다.
💡 언제 pickle·dill을 많이 쓰는가
다음과 같은 상황에서 pickle이나 dill을 고려하는 경우가 많습니다.
- 📦머신러닝 학습 결과(모델, 파이프라인, 스케일러 등)를 그대로 저장하고 다시 불러오고 싶을 때
- 🧪복잡한 실험 세팅(파라미터, 전처리 함수, 커스텀 클래스 인스턴스 등)을 한 번에 스냅샷처럼 남기고 싶을 때
- 🚀프로세스 간에 파이썬 객체를 전달해야 하는데, 단순 문자열이나 숫자만으로는 구조를 표현하기 어려울 때
- 🛠️캐싱을 걸어두고, 다음 실행에서 바로 이전 상태로 워밍업하고 싶은 경우
정리하면 pickle과 dill은 “그냥 데이터” 이상의 것을 다룹니다.
실행 맥락까지 복구 가능한 형태로 저장하고 싶을 때 강력하다는 의미입니다.
이건 JSON이나 CSV 같은 전통적인 포맷으로는 할 수 없는 수준의 복원력입니다.
⚠️ 주의: pickle과 dill은 만능 저장 포맷처럼 보이지만, 어디까지나 파이썬 런타임을 전제로 한 내부용 스냅샷에 가깝습니다.
다른 언어나 환경에서 바로 읽어들일 수 있는 범용 교환 포맷이라고 생각하면 곤란합니다.
import pickle
data = {
"name": "model_state",
"version": 3,
"params": [0.12, 0.98, 0.33],
}
# 직렬화 (dump)
with open("state.pkl", "wb") as f:
pickle.dump(data, f)
# 역직렬화 (load)
with open("state.pkl", "rb") as f:
loaded = pickle.load(f)
print(loaded)
위 예시는 기본 타입만 다뤘지만, 실제로는 클래스 인스턴스나 훨씬 복잡한 구조도 거의 똑같은 방식으로 저장할 수 있습니다.
그래서 체감 난이도는 낮고 효용은 높은 편입니다.
바로 이 지점 때문에 많은 개발자가 pickle과 dill을 편하게 쓰기 시작합니다.
그리고 바로 이 지점부터 보안 이야기가 따라붙습니다.
📌 신뢰할 수 없는 pickle 데이터를 로드하면 안 되는 이유
pickle은 내부적으로 파이썬의 객체를 그대로 복원하기 위해 임의 코드 실행을 허용합니다.
이 말은 곧, 누군가 의도적으로 조작한 pickle 파일을 열면 내 컴퓨터에서 임의의 파이썬 코드가 실행될 수 있다는 뜻입니다.
즉, 신뢰할 수 없는 pickle 파일은 단순 데이터 파일이 아니라 잠재적인 실행 스크립트가 될 수 있습니다.
이건 단순한 버그 수준이 아니라 보안상 치명적인 설계적 한계로, 파이썬 공식 문서에서도 “절대 신뢰할 수 없는 소스의 pickle 데이터를 로드하지 말라”고 명시되어 있습니다.
💬 파이썬 공식 문서: “The pickle module is not secure. Only unpickle data you trust.” — Python 3.12 Documentation
pickle 파일을 로드할 때 내부에서는 객체를 재구성하기 위해 모듈 import, 클래스 생성, 그리고 그 안의 __reduce__ 또는 __setstate__ 같은 메서드를 호출할 수 있습니다.
이 메서드 안에 악의적인 코드가 들어 있다면, 단순히 pickle.load()를 호출하는 것만으로도 시스템 명령이 실행될 수 있는 구조입니다.
이건 웹 서비스나 서버 환경에서는 특히 심각한 취약점으로 이어질 수 있습니다.
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ("echo 해킹됨!",))
# 악성 pickle 생성
with open("evil.pkl", "wb") as f:
pickle.dump(Exploit(), f)
# 이 파일을 불러오기만 해도 명령 실행됨
with open("evil.pkl", "rb") as f:
pickle.load(f)
위 예시는 단순한 데모지만, 실제 공격에서는 이 구조를 이용해 악성 명령을 실행하거나, 서버 내부 정보를 탈취하는 등의 행위가 가능합니다.
특히 웹 기반 서비스에서 pickle을 세션 저장용이나 캐시 직렬화용으로 쓰다가 이런 보안 이슈를 맞는 경우가 많습니다.
즉, pickle은 ‘내가 저장하고 내가 다시 불러오는 개인용 파일’로는 편리하지만, ‘외부 입력을 받아 처리하는 서비스 환경’에서는 절대 쓰면 안 되는 도구입니다.
⚠️ 주의: pickle 파일은 단순 데이터 파일처럼 보이지만, 그 안에 포함된 객체 복원 과정에서 실제 코드가 실행됩니다.
즉, 신뢰할 수 없는 pickle 파일을 열면 그 자체가 악성 스크립트 실행이 될 수 있습니다.
🔒 안전하게 pickle을 쓰는 방법
pickle을 완전히 버릴 필요는 없습니다.
다만 쓸 때 몇 가지 원칙을 철저히 지켜야 합니다.
- 🧩pickle 파일은 오직 내가 생성하고, 내가 읽는 환경에서만 사용합니다.
- 🧱외부에서 받은 pickle 파일은 절대 pickle.load()로 직접 읽지 않습니다.
- 🧮필요하다면 pickle 대신 JSON, msgpack, orc, feather 등 범용 포맷을 고려합니다.
- 🚫pickle 데이터를 웹 API 요청이나 사용자의 업로드로 받아 처리하는 일은 절대 금지합니다.
즉, pickle은 개인 또는 내부 시스템에서만 한정적으로 활용해야 합니다.
외부 노출 가능성이 있는 환경에서는 안전성을 보장하지 못합니다.
이 원칙 하나만 지켜도 대부분의 보안 문제는 예방할 수 있습니다.
📌 pickle 프로토콜 버전과 호환성 이해하기
pickle은 단순히 데이터를 바이너리로 덤프하는 게 아니라, 프로토콜(protocol)이라는 버전 규칙에 따라 데이터를 기록합니다.
이 프로토콜은 Python 버전이 업데이트될 때마다 더 효율적이고 빠르게 개선되어 왔습니다.
예를 들어 Python 3.8 이후에는 protocol 5가 기본으로 적용되면서, 대용량 객체 처리 속도와 버퍼 공유 효율이 크게 향상되었습니다.
즉, pickle.dump() 할 때 사용하는 프로토콜 버전에 따라 파일 크기, 로딩 속도, 그리고 다른 파이썬 버전과의 호환성이 달라집니다.
💬 참고: pickle은 Python 3.x 버전대에서 protocol 0~5까지를 지원하며, Python 3.12 이후에는 더 고도화된 프로토콜이 실험적으로 포함되어 있습니다.
기본적으로 pickle.dump()를 사용할 때는 최신 프로토콜이 자동으로 선택됩니다.
하지만 구버전 파이썬에서 호환성을 유지해야 하는 경우, 명시적으로 낮은 버전을 지정하는 게 좋습니다.
이때 유용한 옵션이 바로 protocol 파라미터입니다.
이를 통해 “어떤 세대의 pickle 규칙으로 직렬화할 것인지”를 직접 정할 수 있습니다.
import pickle
data = {"x": [1, 2, 3], "y": "테스트"}
# 최신 프로토콜로 직렬화
with open("latest.pkl", "wb") as f:
pickle.dump(data, f, protocol=pickle.HIGHEST_PROTOCOL)
# 구버전 호환성을 위해 protocol=3 지정
with open("compat.pkl", "wb") as f:
pickle.dump(data, f, protocol=3)
이렇게 프로토콜 버전을 지정하면, 파이썬 3.4 이상이라면 동일한 파일을 문제없이 로드할 수 있습니다.
단, 너무 낮은 프로토콜을 사용하면 데이터 구조의 일부 기능(예: bytes 객체, 대용량 배열 등)이 효율적으로 저장되지 않을 수 있으니, 환경에 맞게 조정하는 게 중요합니다.
⚙️ 프로토콜 버전별 차이 요약
| 프로토콜 버전 | 특징 및 호환성 |
|---|---|
| 0 | 텍스트 기반, 파이썬 2.x 시절의 완전 호환용 |
| 2 | Python 2와 3에서 모두 사용 가능한 안정 버전 |
| 4 | 대용량 객체와 새로운 타입 지원 (Python 3.4+) |
| 5 | 버퍼 프로토콜 개선, 속도 향상 (Python 3.8+) |
결국 프로토콜은 “얼마나 효율적으로 객체를 표현할 수 있는가”의 문제이자, “내가 어느 환경에서 이 파일을 다시 불러올 것인가”의 기준입니다.
pickle 파일을 배포하거나 장기 보관해야 한다면, 프로토콜 버전까지 메타데이터로 기록해두는 습관이 좋습니다.
💎 핵심 포인트:
pickle 파일은 파이썬 버전 간 호환성을 항상 보장하지 않습니다.
장기 보관 목적이라면 프로토콜 버전과 Python 버전을 함께 명시해두는 것이 안전합니다.
📌 copyreg로 커스텀 클래스 직렬화 규칙 등록하기
pickle은 기본적으로 파이썬 내장 타입이나 단순한 클래스는 자동으로 직렬화할 수 있지만, 모든 객체를 완벽하게 처리하지는 못합니다.
특히 커스텀 클래스의 내부 구조가 복잡하거나, 직렬화할 수 없는 자원을 포함하는 경우(예: 파일 핸들, 네트워크 소켓 등)에는 에러가 발생합니다.
이럴 때 copyreg 모듈을 이용하면 특정 클래스의 직렬화/역직렬화 방법을 직접 등록할 수 있습니다.
이 기능을 활용하면 pickle이 모르는 객체도 우리가 지정한 방식으로 안전하게 직렬화할 수 있습니다.
🧩 copyreg.register_pickle 예시로 이해하기
copyreg은 클래스와 직렬화 함수를 매핑해줍니다.
핵심은 두 가지 함수 — pickle_function(obj)과 unpickle_function(data) — 를 정의하고, 이를 등록하는 구조입니다.
pickle_function은 객체를 직렬화 가능한 형태(튜플 등)로 변환하고, unpickle_function은 그 데이터를 다시 원래 객체로 복원하는 역할을 합니다.
import pickle
import copyreg
class CustomObject:
def __init__(self, name, values):
self.name = name
self.values = values
def serialize_custom(obj):
return (obj.name, obj.values)
def deserialize_custom(name, values):
return CustomObject(name, values)
# copyreg에 등록
copyreg.pickle(CustomObject, serialize_custom, deserialize_custom)
# 테스트
obj = CustomObject("테스트", [1, 2, 3])
with open("custom.pkl", "wb") as f:
pickle.dump(obj, f)
with open("custom.pkl", "rb") as f:
restored = pickle.load(f)
print(restored.name, restored.values)
위 코드를 실행하면 CustomObject 인스턴스를 문제없이 저장하고 다시 복원할 수 있습니다.
copyreg은 내부적으로 pickle이 객체를 덤프할 때 어떤 함수로 처리할지 규칙을 알려주는 역할을 하며, 복잡한 객체 구조를 다루는 데 유용합니다.
💬 참고: copyreg은 dill에서도 동일하게 동작합니다. dill은 pickle의 상위호환 개념이기 때문에 동일한 방식으로 커스텀 규칙을 적용할 수 있습니다.
💡 copyreg를 활용해야 하는 이유
copyreg를 활용하면 다음과 같은 장점이 있습니다.
- 🧱pickle이 처리하지 못하는 사용자 정의 클래스도 직렬화 가능
- 🧮객체 구조가 변경되어도 유연하게 직렬화 포맷을 유지할 수 있음
- 🧩데이터 마이그레이션 시 pickle 호환성 문제를 줄일 수 있음
- 🔄특정 클래스의 저장 방식만 커스터마이징할 수 있어 코드 전반의 일관성 유지 가능
💎 핵심 포인트:
copyreg는 “pickle이 객체를 이해하도록 가르치는 도구”입니다.
커스텀 클래스를 다루는 프로젝트에서는 반드시 알아두면 유용한 직렬화 테크닉입니다.
📌 pickle 대신 생각해볼 수 있는 안전한 대안
pickle은 파이썬에서 매우 편리한 직렬화 도구지만, 보안상 한계와 호환성 문제로 인해 항상 최선의 선택은 아닙니다.
특히 외부 데이터를 다루는 환경에서는 더 안전하고 표준화된 포맷을 고려해야 합니다.
이 섹션에서는 pickle의 대안으로 많이 사용되는 포맷과 라이브러리를 비교해보며, 어떤 상황에서 대체가 가능한지 살펴봅니다.
🧾 JSON – 가장 널리 쓰이는 범용 직렬화 포맷
JSON은 텍스트 기반이라 사람이 읽기 쉽고, 언어나 플랫폼에 상관없이 사용할 수 있습니다.
pickle과 달리 임의 코드 실행의 위험이 없으며, 단순한 자료구조(리스트, 딕셔너리, 문자열 등)를 저장하기에 충분합니다.
다만 클래스나 함수, 복잡한 참조 구조를 그대로 저장할 수 없기 때문에, 구조가 단순한 데이터 교환에 적합합니다.
import json
data = {"name": "user", "score": 95}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
이처럼 JSON은 웹 API, 설정 파일, 로그 저장 등 안전하고 범용적인 상황에서 가장 권장되는 포맷입니다.
📦 MessagePack, Avro, Parquet 등 바이너리 대안
바이너리 직렬화 포맷은 JSON보다 더 빠르고, 파일 크기가 작습니다.
데이터 분석 환경에서는 특히 자주 사용되죠.
대표적인 대안으로는 MessagePack, Avro, Parquet이 있습니다.
| 포맷 | 특징 |
|---|---|
| MessagePack | JSON과 유사하지만, 바이너리 기반으로 훨씬 작고 빠름 |
| Avro | 스키마 기반 직렬화, 빅데이터 시스템(Hadoop, Kafka)에서 자주 사용 |
| Parquet | 컬럼형 저장방식, 대용량 분석 환경에서 효율적 |
이들 포맷은 pickle처럼 객체 그래프를 그대로 복원하진 않지만, 구조적 데이터의 효율적인 저장과 전송에는 훨씬 적합합니다.
🔐 보안 중심이라면 pickle은 최소화가 정답
pickle을 완전히 배제할 필요는 없지만, 다음 원칙만은 지켜야 합니다.
- 🧱pickle은 내부용, 개인 데이터 스냅샷 용도로만 사용
- 🚫네트워크를 통한 pickle 데이터 전송은 절대 금지
- 📜장기 저장 데이터는 JSON, CSV, Parquet 같은 표준 포맷으로 보관
- 💾pickle을 써야 한다면 프로토콜 버전과 Python 버전을 함께 기록
💎 핵심 포인트:
pickle은 빠르고 강력하지만, ‘안전’과 ‘호환성’을 우선시한다면 JSON·MessagePack·Parquet 같은 표준 포맷이 훨씬 더 적합한 선택입니다.
❓ 자주 묻는 질문 (FAQ)
pickle과 dill의 차이는 뭔가요?
기본 원리는 같지만, dill은 더 유연하고 기능적입니다.
pickle 파일은 다른 파이썬 버전에서도 읽을 수 있나요?
프로토콜 버전이 다르거나 클래스 구조가 변경되면 로드 중 오류가 발생할 수 있습니다.
따라서 장기 저장 시에는 프로토콜 버전과 Python 버전을 함께 기록하는 것이 안전합니다.
pickle로 저장된 데이터는 수정할 수 있나요?
수정하려면 원본 객체를 다시 로드하고, 변경 후 새롭게 직렬화해야 합니다.
pickle 대신 JSON을 써야 하는 이유는?
외부 시스템과 데이터 교환 시 표준화된 구조를 제공하고, 보안상 코드 실행 위험이 없습니다.
반면 pickle은 파이썬 환경 전용이라 이식성과 안전성 면에서 한계가 있습니다.
copyreg은 꼭 써야 하나요?
copyreg을 사용하면 클래스 구조 변경에도 유연하게 대응할 수 있어 장기적으로 코드 유지보수가 쉬워집니다.
pickle을 암호화해서 저장하면 안전할까요?
악성 pickle은 복호화 후 여전히 위험하므로 “신뢰할 수 없는 데이터는 로드하지 않는다”는 원칙은 그대로 적용됩니다.
pickle로 NumPy나 pandas 객체도 저장할 수 있나요?
NumPy는 np.save, pandas는 to_pickle 대신 feather, parquet 등 더 안전한 포맷을 권장합니다.
pickle 파일 크기가 너무 커질 때는 어떻게 하나요?
예를 들어 “with gzip.open(‘data.pkl.gz’, ‘wb’)” 형태로 사용하면 자동 압축되어 저장 공간을 절약할 수 있습니다.
🧠 파이썬 직렬화의 핵심, pickle은 ‘내부용 도구’로만 생각하자
pickle은 파이썬 객체를 그대로 저장할 수 있다는 점에서 강력하지만, 동시에 “신뢰할 수 없는 데이터에 사용하면 안 된다”는 중요한 전제가 있습니다.
이 글에서 살펴본 것처럼, pickle과 dill은 객체 그래프 전체를 직렬화할 수 있어 실험 환경이나 머신러닝 모델 저장에 매우 편리합니다.
하지만 이 편리함 뒤에는 보안 리스크가 숨어 있습니다.
임의 코드 실행이 가능한 구조이기 때문에 외부 데이터나 사용자가 업로드한 pickle 파일은 절대 로드해서는 안 됩니다.
또한 pickle의 프로토콜 버전은 호환성에 직접 영향을 미칩니다.
장기 보관용이라면 Python 버전과 프로토콜 버전을 함께 기록하고, 커스텀 클래스를 다룬다면 copyreg를 활용하는 것이 안전합니다.
pickle은 빠르고 직관적이지만, 오직 내가 만든 환경 안에서만 사용하는 것이 올바른 접근입니다.
데이터 교환이나 외부 서비스에서는 JSON, MessagePack, Parquet 같은 안전한 포맷이 훨씬 나은 선택입니다.
결국 파이썬의 직렬화 전략은 ‘편의성 vs 안전성’의 균형을 잡는 일입니다.
pickle은 강력한 스냅샷 도구이지만, 언제나 “신뢰할 수 있는 데이터만 로드한다”는 원칙 아래에서만 사용해야 합니다.
그 점만 명확히 구분한다면, pickle은 여전히 실무와 연구 환경에서 유용한 직렬화 도구로 자리 잡을 수 있습니다.
🏷️ 관련 태그 : pickle, dill, 파이썬직렬화, 데이터보안, 객체그래프, 프로토콜버전, copyreg, 데이터저장, 파이썬팁, 직렬화대안