Введение

На ранней стадии разработки нашего бэкенда мой товарищ по команде познакомил меня с @dataclass, который мы теперь используем, среди прочего, для интерфейса сопоставления полей и построителя запросов. В этой статье я поделюсь своим опытом использования @dataclass в нашем бэкэнд-сервисе.

Я начну с краткого обзора @dataclass, затем его практической реализации и закончу извлеченным уроком.

🤚 Предположения

  • Вы понимаете, как работает объект класса Python

Познакомьтесь с @dataclass, потому что он превосходен!

Я кратко представлю и продемонстрирую, как работает @dataclass.

Чтобы сравнить различия, давайте создадим класс для книги, который принимает имя, автора, цену и издателя.

# using normal class #
class Book:
    def __init__(self, name :str, author :str, publisher :str, price: float) -> None:
        self.name = name
        self.author = author
        self.price = price
        self.publisher = publisher

# using @dataclass #
from dataclasses import dataclass
@dataclass
class Book:
    name: str
    author: str
    publisher: str
    price: float

Хотя @dataclass может показаться не сильно отличающимся от традиционных определений классов, он предлагает несколько преимуществ, которые могут сделать ваш код более кратким и удобным для сопровождения.

Вот несколько преимуществ использования @dataclass, которые мне нравятся:

лаконичность

Используя @dataclass, вы можете определить класс с значительно меньшим объемом шаблонного кода. Декоратор автоматически генерирует такие методы, как __init__, __repr__ и __eq__, на основе определяемых вами атрибутов класса, что сокращает объем кода, который необходимо писать и поддерживать.

Аннотации типов

@dataclass поддерживает аннотации типов, что делает код более явным и понятным. Эта функция также может помочь предотвратить ошибки за счет принудительной проверки типов при создании экземпляра класса.

Использование @dataclass — простая идея, и здесь не так много подводных камней, за исключением правильного использования. Например, если класс, который я создаю, имеет всего несколько атрибутов и множество методов класса, я бы не стал использовать @dataclass, но это все же возможно.

Разумно заключить, что @dataclass предлагает более удобный подход к созданию класса, который хранит данные, как следует из названия.

Как я уже говорил в своей предыдущей статье Идиоматический Python: как я научился использовать декораторы, знание функции может быть полезным, но еще полезнее наблюдать за ее практической реализацией. Итак, давайте посмотрим, как @dataclass можно использовать на практике.

@dataclass на практике

Наша серверная служба поиска взаимодействует с различными решениями для хранения данных, и для удовлетворения запросов клиентов она должна эффективно отображать соответствующие имена полей. Эта задача может быть сложной, поскольку для разных типов хранилищ могут потребоваться другие форматы имен полей.

Например, когда клиент запрашивает набор данных, содержащий поле содержимого, серверной части необходимо по-разному обрабатывать имя поля в зависимости от того, выполняется ли поиск в хранилище данных SQL или NoSQL. В случае хранения данных SQL поле следует интерпретировать как «body_text_plain». Однако при поиске в хранилище данных NoSQL поле следует интерпретировать как «body.text/plain».

Несколько имен путей в центральном расположении

Мы стремились централизовать картографирование полей в одном месте для большей эффективности. Прежде чем принять @dataclass, мы использовали словарь для сопоставления каждого имени поля для каждого хранилища данных:

field_mapper = {
  "content": {
    "sql_path": "body_text_plain",
    "nosql_path": "body.text/plain",
    "description": "body text field"
  }
  # ... other fields
}

Хотя словари могут выполнять сопоставление полей, поддерживать их становится все труднее по мере роста числа полей. Написание или копирование и вставка каждого ключа может занять много времени, и их изменение или удаление не менее сложно.

Замена словаря на @dataclass

Чтобы решить проблему ремонтопригодности, давайте создадим класс с @dataclass, где каждое поле будет назначено как экземпляр класса класса Field:

### using @dataclass ###
from typing import Optional
from dataclasses import dataclass

@dataclass
class Field:
    name: str
    description: str
    sql_path: Optional[str] = None
    nosql_path: Optional[str] = None
    
content = Field(
    name="content",
    description="text field",
    sql_path="body_text_plain",
    nosql_path="body.text/plain"
)
title = Field(
    name="title",
    description="title field",
    sql_path="body_title",
    nosql_path="body.title"
)
# ... more fields

Использование @dataclass позволило нам сократить объем необходимых работ по обслуживанию. Кроме того, каждый экземпляр класса Field теперь вызывается для сборки на основе использования соответствующего запроса.

Поэтому, если вы столкнулись с проблемой, требующей хранения данных, возможно, пришло время подумать об использовании @dataclass.

Урок выучен

Чтобы научиться использовать @dataclass, не потребовалось много времени. Мне потребовалось много времени, чтобы полностью охватить его и правильно использовать на практике. В ходе этого процесса я осознал недостаточное знание встроенных функций и возможностей Python, что побудило меня прочитать дополнительные образовательные ресурсы, посвященные продвинутым концепциям Python.

Использование таких функций, как @dataclass, научило меня думать о более инновационных и потенциально «питоновских» подходах к решению моих проблем. Python предлагает множество функций, которые я начинаю открывать для себя. Я буду делиться другими подобными статьями по мере того, как мое путешествие продолжается.

Я надеюсь, что эта статья заинтересует вас в изучении @dataclass и поиске других «Pythonic» способов решения проблем.

Спасибо за чтение!

Чтобы узнать больше о реализации классов данных, ознакомьтесь с этой статьей от RealPython.