Практическое руководство: как сериализовать данные в Python
Основные форматы сериализации в Python
Как сериализовать данные в JSON для обмена между разными языками программирования?
JSON (JavaScript Object Notation) - текстовый формат, поддерживаемый практически всеми языками. В Python для работы с JSON используется модуль json.
Пример:
import json
data = {"name": "Alice", "age": 30, "city": "Moscow"}
json_string = json.dumps(data, ensure_ascii=False, indent=2)
print(json_string)
форматы данных python (форматы данных в python)
{
"name": "Alice",
"age": 30,
"city": "Moscow"
}
Пояснение: функция dumps преобразует объект Python в строку JSON. Параметр ensure_ascii=False позволяет сохранять не-ASCII символы (например, кириллицу). Параметр indent добавляет отступы для читаемости.
Для записи в файл используется dump:
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
Для чтения из файла - load.
Типичные проблемы:
- объекты типа datetime, Decimal, пользовательские классы не сериализуются по умолчанию (вызывается TypeError). Решение - использовать параметр default в dumps или создать собственный класс-наследник json.JSONEncoder.
- попытка декодировать некорректную строку JSON вызывает json.JSONDecodeError.
Решение для datetime:
from datetime import datetime
def my_converter(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
json_string = json.dumps({"time": datetime.now()}, default=my_converter)
Цели использования: JSON идеально подходит для веб-API, конфигурационных файлов, обмена данными между сервисами. Он читаемый, легкий и поддерживается повсеместно.
Как сериализовать данные в XML для работы с документами и веб-сервисами?
XML (eXtensible Markup Language) - структурированный формат, широко применяемый в документах, конфигурациях (например, Android-манифесты) и веб-сервисах (SOAP). В Python есть несколько библиотек, самая простая - xml.etree.ElementTree.
Пример:
import xml.etree.ElementTree as ET
data = {"name": "Alice", "age": 30}
root = ET.Element("person")
for key, value in data.items():
child = ET.SubElement(root, key)
child.text = str(value)
tree = ET.ElementTree(root)
tree.write("person.xml", encoding="utf-8", xml_declaration=True)
Результат - файл person.xml с содержимым:
<?xml version="1.0" encoding="utf-8"?> <person> <name>Alice</name> <age>30</age> </person>
Пояснение: создается корневой элемент <person>, в него добавляются дочерние элементы. Текстовое значение задается через атрибут .text. Запись в файл осуществляется методом write.
Типичные проблемы:
- сложные структуры (списки, вложенные объекты) требуют ручного построения дерева.
- отсутствие автоматической сериализации Python-объектов - все атрибуты нужно преобразовывать в строки вручную.
- большие файлы создаются с потреблением памяти.
Решение: для сложных структур можно использовать lxml с поддержкой XPath и более удобным API, либо библиотеку-генератор, например xmltodict.
Цели использования: XML незаменим, когда требуется строгая схема (XSD), поддержка пространств имен, работа с документами (XHTML, SVG). Также часто встречается в старых системах.
Как сериализовать данные в YAML для удобного чтения человеком?
YAML (YAML Ain't Markup Language) - формат, ориентированный на читабельность, часто используется для конфигурационных файлов (Docker Compose, Ansible). В Python требуется сторонняя библиотека PyYAML.
Пример:
import yaml
config = {
"server": {
"host": "localhost",
"port": 8080,
"debug": True
},
"database": {
"name": "mydb",
"user": "admin"
}
}
with open("config.yaml", "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
Содержимое config.yaml:
database: name: mydb user: admin server: debug: true host: localhost port: 8080
Пояснение: функция yaml.dump преобразует словарь в YAML-формат. Параметр default_flow_style=False задает блочный стиль (нагляднее). allow_unicode=True поддерживает кириллицу.
Типичные проблемы:
- небезопасная десериализация - при использовании yaml.load без указания Loader=yaml.SafeLoader может выполнить произвольный код (если YAML содержит теги). Рекомендуется всегда использовать yaml.safe_load.
- сложные объекты (например, нестандартные классы) требуют кастомных representer.
Решение: для безопасной загрузки применять safe_load, а для сериализации своих классов - зарегистрировать representer.
Цели использования: YAML отлично подходит для конфигураций, где важна читаемость человеком, при написании скриптов автоматизации (например, CI/CD) и инструментов DevOps.
Как сериализовать данные с помощью pickle для быстрой передачи объектов Python?
pickle - модуль для сериализации произвольных Python-объектов в бинарный формат. Он уникален тем, что может сериализовать почти всё, включая функции, классы, исключения, но только для Python.
Пример:
import pickle
data = {"name": "Bob", "nested": [1, 2, 3], "func": lambda x: x*2}
with open("data.pkl", "wb") as f:
pickle.dump(data, f)
with open("data.pkl", "rb") as f:
loaded = pickle.load(f)
print(loaded)
Вывод:
{'name': 'Bob', 'nested': [1, 2, 3], 'func': at 0x...>}
Пояснение: объект (включая лямбда-функцию) был сериализован и восстановлен. Файл открывается в бинарном режиме ("wb"/"rb").
Типичные проблемы:
- небезопасность - загрузка pickle из ненадежного источника может выполнить вредоносный код.
- несовместимость версий Python - pickle одного мажорного релиза может не читаться в другой версии.
- невозможность обмена данными с другими языками.
Решение: использовать pickle только для внутренних задач, где безопасность гарантирована. Для обмена с другими системами применять JSON или аналоги.
Цели использования: кэширование объектов, сохранение состояния между запусками приложения, быстрая передача данных между разными процессами на одном компьютере (multiprocessing).
Расширенные примеры и нестандартные сценарии
Здесь приведены примеры, которые выходят за рамки базового использования, но могут оказаться полезными в реальных проектах.
Кастомный JSON-кодировщик для дат, десятичных чисел и dataclass
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
import json
@dataclass
class Product:
name: str
price: Decimal
created: datetime
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, Decimal):
return float(obj)
if isinstance(obj, Product):
return {'name': obj.name, 'price': obj.price, 'created': obj.created}
return super().default(obj)
product = Product(name="Laptop", price=Decimal('1299.99'), created=datetime.now())
json_str = json.dumps(product, cls=CustomEncoder, indent=2, ensure_ascii=False)
print(json_str)
{
"name": "Laptop",
"price": 1299.99,
"created": "2025-03-28T12:34:56.789123"
}
Пояснение: класс CustomEncoder переопределяет метод default, который вызывается для объектов, не поддерживаемых стандартным кодировщиком. В примере обработаны три типа. Для dataclass можно также использовать библиотеку dataclasses_json.
Потоковая обработка большого XML-файла с iterparse
import xml.etree.ElementTree as ET
# Предположим, есть большой файл large.xml с тысячами элементов -
def process_items(filepath):
for event, elem in ET.iterparse(filepath, events=('end',)):
if elem.tag == 'item':
# извлекаем данные
name = elem.findtext('name')
price = elem.findtext('price')
print(f"Item: {name}, Price: {price}")
# очищаем обработанный элемент, чтобы не накапливать память
elem.clear()
elif elem.tag == 'root':
break
# Использование: process_items('large.xml')
Пояснение: функция iterparse генерирует события в процессе разбора, не загружая весь документ в память. После обработки элемента вызов elem.clear() освобождает память. Это важно для файлов размером сотни мегабайт и более.
Проблема: при неверном использовании (например, отсутствие clear) память может расти. Также необходимо обрабатывать события в правильном порядке.
YAML с пользовательскими тегами для безопасной десериализации
import yaml
from datetime import datetime
def datetime_representer(dumper, data):
return dumper.represent_scalar('tag:yaml.org,2002:timestamp', data.isoformat())
yaml.add_representer(datetime, datetime_representer)
data = {'event': 'meeting', 'timestamp': datetime.now()}
yaml_str = yaml.dump(data, allow_unicode=True)
print(yaml_str)
event: meeting timestamp: 2025-03-28T12:34:56.789123
Загрузка с кастомным конструктором (безопасно через SafeLoader):
def datetime_constructor(loader, node):
value = loader.construct_scalar(node)
return datetime.fromisoformat(value)
yaml.add_constructor('tag:yaml.org,2002:timestamp', datetime_constructor, Loader=yaml.SafeLoader)
loaded = yaml.safe_load(yaml_str)
print(loaded)
{'event': 'meeting', 'timestamp': datetime.datetime(2025, 3, 28, 12, 34, 56, 789123)}
Пояснение: добавление собственных representer и constructor расширяет возможности сериализации без потери безопасности. Всегда используйте SafeLoader для предотвращения выполнения кода.
Передача сложных объектов через socket с помощью pickle
import pickle
import socket
# Сервер (слушает порт)
def server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('localhost', 12345))
s.listen(1)
conn, addr = s.accept()
data = conn.recv(4096)
obj = pickle.loads(data)
conn.send(pickle.dumps({'result': obj['x'] + obj['y']}))
conn.close()
# Клиент
def client():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 12345))
obj = {'x': 10, 'y': 20, 'func': lambda: None} # лямбда сериализуема
s.send(pickle.dumps(obj))
response = s.recv(4096)
print(pickle.loads(response))
s.close()
Пояснение: pickle может передавать даже лямбда-функции, что удобно для распределенных вычислений (например, с multiprocessing), но небезопасно в открытом Интернете. В реальном коде следует тщательно контролировать источник данных.
Проблема: если объект содержит ссылки на внешние ресурсы (например, открытый файл), pickle может их не корректно восстановить.
Использование JSON Lines для хранения записей в логах
import json
logs = [
{"timestamp": "2025-03-28T10:00:00", "level": "INFO", "msg": "Started"},
{"timestamp": "2025-03-28T10:01:00", "level": "ERROR", "msg": "Timeout"}
]
# Запись
with open("logs.jsonl", "w", encoding="utf-8") as f:
for entry in logs:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
# Чтение
with open("logs.jsonl", "r", encoding="utf-8") as f:
for line in f:
entry = json.loads(line.strip())
print(entry)
{'timestamp': '2025-03-28T10:00:00', 'level': 'INFO', 'msg': 'Started'}
{'timestamp': '2025-03-28T10:01:00', 'level': 'ERROR', 'msg': 'Timeout'}
Пояснение: JSON Lines (каждая строка - отдельный JSON-объект) позволяет читать и дописывать файл без загрузки всего содержимого в память. Удобно для логов, потоковой передачи данных.