Сериализация и десериализация (pickle)
Условие
Объясните, что такое pickling и unpickling в Python.
Задача
- Сериализуйте Python-объект в файл
- Десериализуйте его обратно
- Объясните риски безопасности при использовании pickle
- Какие альтернативы существуют (json, msgpack)?
Пример
import pickle
data = {"name": "Alice", "age": 30}
# Сохраните в файл и загрузите обратно
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Сериализация и десериализация (pickle)
Что такое Pickling?
Pickling — это процесс преобразования Python объекта в байтовый поток (serialization). Unpickling — обратный процесс восстановления объекта из байтов (deserialization).
Essentially, pickle позволяет сохранить состояние Python объекта и восстановить его позже, даже если программа закончилась.
1. Базовое использование pickle
import pickle
from typing import Any
# СЕРИАЛИЗАЦИЯ (pickle)
data = {"name": "Alice", "age": 30, "tags": ["python", "django"]}
# Способ 1: Сохранить в файл
with open("data.pkl", "wb") as f: # 'wb' = write binary
pickle.dump(data, f)
# Способ 2: Получить байты напрямую
bytes_data = pickle.dumps(data) # 's' = returns bytes
print(type(bytes_data)) # <class 'bytes'>
print(bytes_data[:50]) # b'\x80\x04}\x94(X\x04\x00\x00\x00name...'
# ДЕСЕРИАЛИЗАЦИЯ (unpickle)
# Способ 1: Из файла
with open("data.pkl", "rb") as f: # 'rb' = read binary
restored_data = pickle.load(f)
print(restored_data) # {'name': 'Alice', 'age': 30, 'tags': ['python', 'django']}
print(restored_data == data) # True
# Способ 2: Из байтов
restored = pickle.loads(bytes_data) # 's' = takes bytes
print(restored) # {'name': 'Alice', 'age': 30, 'tags': ['python', 'django']}
2. Сериализация сложных объектов
import pickle
from datetime import datetime
from dataclasses import dataclass
@dataclass
class User:
"""Пользовательский класс"""
id: int
name: str
email: str
created_at: datetime
def greet(self):
return f"Hello, {self.name}!"
# Создаем объект
user = User(
id=1,
name="Alice",
email="alice@example.com",
created_at=datetime.now()
)
# Сохраняем
with open("user.pkl", "wb") as f:
pickle.dump(user, f)
# Загружаем
with open("user.pkl", "rb") as f:
loaded_user = pickle.load(f)
print(loaded_user.name) # Alice
print(loaded_user.greet()) # Hello, Alice!
print(loaded_user == user) # True
print(type(loaded_user)) # <class '__main__.User'>
Важно: Для unpickle класс должен быть доступен (импортирован).
3. Протоколы Pickle (версии)
import pickle
data = {"name": "Bob", "age": 25}
# Разные протоколы pickle
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
serialized = pickle.dumps(data, protocol=protocol)
print(f"Protocol {protocol}: {len(serialized)} bytes")
# Protocol 0: 54 bytes (ASCII, читаемо)
# Protocol 1: 47 bytes (старше, совместимость)
# Protocol 2: 43 bytes
# Protocol 3: 43 bytes
# Protocol 4: 43 bytes
# Protocol 5: 42 bytes (новый, Python 3.8+, быстрый)
Рекомендация: Используй protocol=pickle.HIGHEST_PROTOCOL для лучшей производительности.
4. РИСКИ БЕЗОПАСНОСТИ ⚠️
КРИТИЧЕСКИ ВАЖНО: Никогда не используй pickle для untrusted данных!
import pickle
import os
# ОПАСНОСТЬ: Malicious pickle может выполнить произвольный код
malicious_data = b'\x80\x03cbuiltins\neval\nq\x00X\x0b\x00\x00\x00os.system("rm -rf /")q\x01\x85q\x02Rq\x03.'
# ❌ НИКОГДА НЕ ДЕЛАЙ ЭТОГО с untrusted данными!
# pickle.loads(malicious_data) # Выполнит os.system()!
# Правильный способ: используй json или другие безопасные форматы
Почему pickle опасен:
- Может выполнять произвольный код при deserialization
- Позволяет вызывать любые Python функции
- Может удалять файлы, кражу данные и т.д.
5. Альтернативы (безопасные)
JSON (самый безопасный)
import json
from datetime import datetime
from typing import Any
# JSON поддерживает: dict, list, str, int, float, bool, null
data = {
"name": "Alice",
"age": 30,
"tags": ["python", "django"],
"is_active": True,
"salary": None
}
# СЕРИАЛИЗАЦИЯ
with open("data.json", "w") as f:
json.dump(data, f, indent=2)
# JSON файл (читаемо):
# {
# "name": "Alice",
# "age": 30,
# ...
# }
# ДЕСЕРИАЛИЗАЦИЯ
with open("data.json", "r") as f:
loaded_data = json.load(f)
print(loaded_data)
# Для datetime нужен custom encoder
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
data_with_date = {"created_at": datetime.now()}
json_str = json.dumps(data_with_date, cls=DateTimeEncoder)
print(json_str) # {"created_at": "2024-01-15T10:30:45.123456"}
Преимущества:
- Безопасен для untrusted данных
- Читаемо
- Поддерживается везде (JavaScript, Java, и т.д.)
- Меньше размер файла
Недостатки:
- Медленнее pickle
- Не поддерживает все типы Python (datetime, custom классы)
MessagePack (бинарный, быстрый)
import msgpack
data = {"name": "Alice", "age": 30}
# СЕРИАЛИЗАЦИЯ
serialized = msgpack.packb(data)
print(type(serialized)) # <class 'bytes'>
print(len(serialized)) # Меньше чем JSON
# Сохраняем
with open("data.msgpack", "wb") as f:
f.write(serialized)
# ДЕСЕРИАЛИЗАЦИЯ
with open("data.msgpack", "rb") as f:
restored = msgpack.unpackb(f.read())
print(restored) # {b'name': b'Alice', b'age': 30}
Преимущества:
- Быстро как pickle
- Безопасно
- Компактно
- Поддерживается многими языками
YAML (читаемо, но медленно)
import yaml
data = {"name": "Alice", "age": 30, "tags": ["python", "django"]}
# СЕРИАЛИЗАЦИЯ
with open("data.yaml", "w") as f:
yaml.dump(data, f)
# YAML файл (очень читаемо):
# name: Alice
# age: 30
# tags:
# - python
# - django
# ДЕСЕРИАЛИЗАЦИЯ
with open("data.yaml", "r") as f:
restored = yaml.safe_load(f) # safe_load! не load!
Важно: Используй yaml.safe_load(), а не yaml.load() (как pickle, может выполнить код).
Сравнение форматов
| Формат | Скорость | Размер | Безопасность | Читаемо | Типы |
|---|---|---|---|---|---|
| Pickle | Очень быстро | Средне | ❌ Опасно | Нет | Все Python |
| JSON | Медленно | Средне | ✅ Безопасно | ✅ Да | Базовые |
| MessagePack | Быстро | Малко | ✅ Безопасно | Нет | Базовые |
| YAML | Медленно | Средне | ✅ safe_load | ✅ Да | Базовые |
| Protobuf | Очень быстро | Очень мало | ✅ Безопасно | Нет | Schema |
Лучшие практики
# ❌ ПЛОХО
import pickle
data = pickle.loads(untrusted_bytes) # Опасно!
# ✅ ХОРОШО
import json
data = json.loads(untrusted_str) # Безопасно
# ✅ ХОРОШО: Pickle только для trusted данных
with open("my_data.pkl", "wb") as f:
pickle.dump(my_object, f)
# ✅ ХОРОШО: Использовать protocol=pickle.HIGHEST_PROTOCOL
pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
# ✅ ХОРОШО: Используй JSON для API и межсервисного взаимодействия
import json
response = json.dumps({"status": "ok", "data": data})
Правило выбора
- Untrusted данные → JSON (безопасно)
- Собственные объекты, нужна скорость → Pickle (but trusted source only)
- Межсервисное взаимодействие → JSON или Protobuf
- Локальный cache, производительность критична → MessagePack
- Конфигурационные файлы → YAML или JSON