Введение

Если вы только начали или уже программировали с использованием Python и любите объектно-ориентированное программирование, но не знакомы с модулем dataclasses, вы пришли в нужное место!

В этой статье мы узнаем:

  • Что такое классы данных и в чем их преимущества.
  • Чем именно они отличаются от обычных классов Python.
  • И когда их следует использовать.

Справочная информация о классах данных

Классы данных используются в основном для моделирования данных в Python. Он украшает обычные классы Python и не имеет ограничений, что означает, что он может вести себя как типичный класс.

Небольшой пример класса данных:

from dataclasses import dataclass
@dataclass
class Car:
   color: str
   manufacturer: str
   top_speed_km: int

dataclasses был введен в Python 3.7 как часть PEP 557.

Давайте погрузимся в некоторые примеры кода

Преимущества класса данных

Встроенная реализация специальных методов

При использовании декоратора @dataclass нам не нужно самим реализовывать специальные методы, что помогает нам избежать шаблонного кода, например, метода init (_init_), метод строкового представления (_repr_), методы, используемые для упорядочения объектов (например, lt, le, gt > и ge), они сравнивают класс, как если бы он был кортежем его полей, по порядку.
Прочтите о нескольких других дополнительных встроенных функциях. методы в официальной документации.

Как это будет выглядеть с обычным классом:

class Car:
  color: str
  manufacturer: str
  top_speed_km: int
  def  __init__(self, color: str, manufacturer: str, top_speed_km: bool):
    self.color = color
    self.manufacturer = manufacturer
    self.top_speed_km = top_speed_km
  def __lt__(self, other_car):
      return self.top_speed_km < other_car.top_speed_km
red_ferrari = Car(color='red', manufacturer='Ferrari', top_speed_km=320)
print(red_ferrari) # <__main__.Car object at 0x7f218789ca00>
black_ferrari = Car(color='red', manufacturer='Ferrari', top_speed_km=347)
print(red_ferrari < black_ferrari) # True

Обратите внимание на эти два момента:

  • Поскольку мы не реализовали специальный метод _repr_, при печати экземпляра Car мы получаем имя класса и адрес объекта.
  • Чтобы сравнить два экземпляра Car, мне пришлось реализовать метод «меньше чем» (_lt_) самостоятельно.

Пример с декоратором класса данных:

from dataclasses import dataclass
@dataclass(order=True)
class Car:
  color: str
  manufacturer: str
  top_speed_km: int
slow_tesla = Car(top_speed_km=261, color='white', manufacturer='Tesla')
print(slow_tesla) # Car(color='white', manufacturer='Tesla', top_speed_km=261)
fast_tesla = Car(top_speed_km=280, color='white', manufacturer='Tesla')
print(slow_tesla < fast_tesla) # True

Необходимо установить order=True, если мы хотим, чтобы реализация методов специального порядка была включена в класс данных (например, lt)

  • Когда мы пытаемся напечатать объект slow_tesla, мы видим фактические значения объекта, а не адрес объекта, в отличие от предыдущего примера.
  • Мы можем сравнивать два объекта без необходимости реализации специальных методов.

Наследование

Как и в случае с обычными классами Python, наследование здесь тоже может принести нам пользу, не нужно иметь дело с конструкцией родительского класса:

from dataclasses import dataclass
@dataclass
class Car:
  color: str
  manufacturer: str
  top_speed_km: int
@dataclass
class ElectricCar(Car):
  battery_capacity_kwh: int
  maximum_range_km: int
white_tesla_model_3 = ElectricCar(color='white', manufacturer='Tesla', top_speed_km=261, battery_capacity_kwh=50, maximum_range_km=455)
print(white_tesla_model_3)
# ElectricCar(color='white', manufacturer='Tesla', top_speed_km=261, battery_capacity_kwh=50, maximum_range_km=455)

Просто для справки, вот как это будет выглядеть при использовании обычного класса:

class Car:
  color: str
  manufacturer: str
  top_speed_km: int
  def  __init__(self, color: str, manufacturer: str, top_speed_km: int):
    self.color = color
    self.manufacturer = manufacturer
    self.top_speed_km = top_speed_km
class ElectricCar(Car):
  battery_capacity_kwh: int
  maximum_range_km: int
  def __init__(self, color: str, manufacturer: str, top_speed_km: int, battery_capacity_kwh: int, maximum_range_km: int):
      super().__init__(color, manufacturer, top_speed_km)
      self.battery_capacity_kwh = battery_capacity_kwh
      self.maximum_range_km: maximum_range_km
white_tesla_model_3 = ElectricCar(color='white', manufacturer='Tesla', top_speed_km=261, battery_capacity_kwh=50, maximum_range_km=455)
print(white_tesla_model_3)

Надеюсь, вы видите, что мы сэкономили много стандартного кода даже в этом маленьком фрагменте кода и не повторяли инициализацию каждого параметра.

Замороженные экземпляры

Передавая frozen=True декоратору класса данных, мы можем создавать неизменяемые объекты Python.

from dataclasses import dataclass
@dataclass(frozen=True)
class Car:
  color: str
  manufacturer: str
  top_speed_km: int

white_tesla = Car(color='white', manufacturer='Tesla', top_speed_km=261)
white_tesla.color = 'Red'

Попытка изменить white_tesla на красную теслу выдаст нам сообщение об ошибке FrozenInstanceError:

dataclasses.FrozenInstanceError: cannot assign to field 'color'

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

Теперь ваша очередь:

  • Знакомы с тем, что такое класс данных и как его использовать.
  • Узнали о преимуществах и вариантах использования классов данных.
  • Имейте несколько примеров кода, которые помогут вам начать работу.
  • Можете начать использовать его в своих проектах.

Заключение

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

Дополнительные ресурсы

https://docs.python.org/3/library/dataclasses.html
https://realpython.com/python-data-classes/