Практическое руководство: как сериализовать данные в 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-объект) позволяет читать и дописывать файл без загрузки всего содержимого в память. Удобно для логов, потоковой передачи данных.

Форматы данных в Python - comments

En
форматы данных python (python)