Сериализация в Python: pickle, json, msgpack — что выбрать
Три способа превратить объект Python в байты и обратно. У каждого своя цена: pickle открывает дыру в безопасности, json теряет типы, msgpack требует зависимости. Разбираем, когда что.

Вам нужно сохранить объект Python в файл, положить в Redis или передать по сети. Три очевидных инструмента — pickle, json, msgpack — дают разный результат: один десериализует любой объект, но выполнит чужой код при загрузке чужого файла; второй читается человеком, но не знает про datetime и set; третий компактнее и быстрее, но тянет зависимость и так же неудобен для отладки. Ниже — где каждый выигрывает и чем платит. Примеры проверены на CPython 3.12, msgpack 1.0.x.
Что вообще происходит при сериализации
Сериализация — превращение объекта в поток байтов, который позже можно восстановить. Разница между тремя форматами не в скорости как таковой, а в трёх вопросах: какие типы формат умеет представлять, кто должен уметь это прочитать, и что случится, если байты пришли от недоверенного источника.
Главное правило: формат выбирается не по бенчмарку, а по границе доверия. Данные ходят внутри вашего процесса — берите быстрое. Данные приходят снаружи — забудьте про pickle.
pickle — родной формат Python и его цена
pickle сериализует почти всё: вложенные объекты, кастомные классы, ссылки друг на друга, замыкания через сторонние библиотеки. Именно поэтому его использует multiprocessing для передачи объектов между процессами и многие кэши по умолчанию.
import pickle
data = {"user_id": 42, "tags": {"vip", "beta"}, "scores": [1, 2, 3]}
# в байты
blob = pickle.dumps(data)
# обратно в объект
restored = pickle.loads(blob)
print(restored["tags"]) # {'vip', 'beta'} — set сохранился как есть
Обратите внимание: set восстановился без потерь. Ни json, ни msgpack так не умеют без ручной обработки.
Почему pickle опасен
При десериализации pickle может вызывать методы объекта (__reduce__). Специально сконструированный pickle-поток выполнит произвольный код в момент pickle.loads(). Это не теоретическая угроза, а штатное поведение формата — так работает восстановление сложных объектов. Официальная документция прямо предупреждает: никогда не десериализуйте данные из недоверенного или неаутентифицированного источника.
Практический вывод: если байты пришли из HTTP-запроса, из файла, который загрузил пользователь, или из очереди, куда пишет внешняя система — pickle нельзя. Даже подпись HMAC поверх pickle помогает только если ключ не утёк, а это лишняя точка отказа.
json — читаемый, переносимый, но узкий по типам
json из стандартной библиотеки безопасен при десериализации: json.loads() не выполняет код, а строит только словари, списки, строки, числа, булевы и None. Формат читается человеком и понимается любым языком.
import json
from datetime import datetime
data = {"user_id": 42, "created": datetime.now()}
# так упадёт: TypeError: Object of type datetime is not JSON serializable
# json.dumps(data)
# нужен свой сериализатор
def encode(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Не умею сериализовать {type(obj)}")
text = json.dumps(data, default=encode)
print(text) # {"user_id": 42, "created": "2024-..."}
Цена читаемости — потеря типов. datetime, Decimal, set, кортеж (превращается в список), байты — всё это json либо не умеет, либо искажает. При десериализации "2024-01-01T00:00:00" останется строкой: json не знает, что это была дата, восстанавливать тип придётся вам вручную.
msgpack — двоичный json, компактнее и быстрее
msgpack — не входит в стандартную библиотеку, ставится через pip install msgpack. Модель типов близка к json (те же словари, списки, числа, строки), но сериализует в двоичный вид: меньше байтов и, как правило, быстрее парсинг. Есть отдельная поддержка bytes, чего json из коробки не даёт.
import msgpack
data = {"user_id": 42, "payload": b"\x00\x01\x02", "scores": [1, 2, 3]}
blob = msgpack.packb(data, use_bin_type=True)
restored = msgpack.unpackb(blob, raw=False)
print(restored["payload"]) # b'\x00\x01\x02' — bytes сохранились
Два параметра, о которые чаще всего спотыкаются: use_bin_type=True при упаковке и raw=False при распаковке. Без них строки и байты могут смешаться. В новых версиях msgpack поведение по умолчанию сдвинулось в сторону строк, но задавать параметры явно надёжнее, чем полагаться на дефолт версии.
Как и json, msgpack не знает про datetime и set без ручных хуков (default при упаковке, object_hook при распаковке). При десериализации msgpack тоже не выполняет код — по безопасности он в одном ряду с json, а не с pickle.
Сравнение
| Критерий | pickle | json | msgpack |
|---|---|---|---|
| В стандартной библиотеке | да | да | нет, pip |
| Читается человеком | нет | да | нет |
| Кросс-язычный | только Python | да | да |
| Кастомные классы «из коробки» | да | нет | нет |
| set, datetime без хуков | да | нет | нет |
| bytes без обвязки | да | нет | да |
| Безопасен для недоверенных данных | нет | да | да |
| Компактность | средняя | низкая | высокая |
Когда что выбирать
- Данные не выходят за пределы вашего процесса и вашего кода (кэш внутри одного сервиса,
multiprocessing, временные файлы, которые пишет и читает только ваше приложение) — pickle. Он единственный сериализует произвольные объекты без ручной обвязки. - Данные пересекают границу доверия или языка — API, вебхуки, конфиги, обмен с фронтендом, логи для человека — json. Читаемость и безопасность важнее компактности.
- Внутренний обмен между сервисами с большим объёмом трафика, где важен размер и скорость, а формат читают только машины — msgpack. Но убедитесь, что зависимость оправдана: ради пары мегабайт в сутки её тянуть незачем.
Отдельно: если объекты сложные, а канал недоверенный — не ищите способ «безопасно распиклить». Опишите явную схему (например, через pydantic или dataclass с ручным маппингом) и гоняйте json/msgpack. Явная схема заодно ловит битые данные на входе.
Что ломается на практике
- pickle между версиями Python и версиями ваших классов. Файл, записанный в 3.10, обычно читается в 3.12, но если вы переименовали класс или переместили модуль —
pickle.loads()упадёт сModuleNotFoundErrorилиAttributeError, потому что pickle хранит путь к классу, а не его определение. Для долгого хранения pickle плохо подходит. - json и целые числа как ключи словаря.
json.dumps({1: "a"})превратит ключ в строку"1", и после загрузки это уже строка. Кортеж-ключ вообще не сериализуется. - msgpack и лимиты. У
unpackbесть ограничения на размер (max_str_lenи подобные) для защиты от раздувания памяти при распаковке недоверенных данных. При работе с большими сообщениями их приходится поднимать явно — иначе получитеValueError. - Порядок ключей. Не полагайтесь на порядок полей в словаре как на часть контракта: разные формы сериализации по-разному его сохраняют.
Что делать дальше
Прежде чем выбрать формат, ответьте на один вопрос: кто пишет байты, которые вы будете читать. Если ответ «только мой код на этой же версии» — pickle экономит вам обвязку. Любой другой ответ — json по умолчанию, msgpack когда упрётесь в размер.
Дальше: если данные хранятся долго, добавьте в них поле версии схемы — это спасёт при миграции. Для проверки входных данных на границе смотрите в сторону pydantic (валидация из json) — он снимает большую часть ручного маппинга типов. А если решите остаться на pickle внутри доверенного контура, задайте protocol=pickle.HIGHEST_PROTOCOL для скорости и компактности и зафиксируйте минимальную версию Python в требованиях проекта.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: Python docs — pickle module (предупреждение о безопасности), msgpack for Python — документация
Частые вопросы
Что если pickle-файл записан в Python 3.10, а читаю в 3.12?
Можно ли безопасно распиклить данные от пользователя, если добавить подпись?
Почему msgpack путает строки и байты?
Что быстрее — json или msgpack?
Как сохранить datetime, если json его не умеет?
А если msgpack падает с ValueError при распаковке большого сообщения?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.