⚖️ Старый и современный подход

⚡ Различия в двух словах

Старый: класс без подсказок типов, ручное __init__, атрибуты класса выглядят так же, как атрибуты объекта.

Новый: аннотации типов, явное разделение классовых и объектных полей через ClassVar, автоматическая генерация __init__, __repr__ и других методов с помощью @dataclass.

Вывод: современный код читать проще, статические анализаторы ловят ошибки ещё до запуска.

📜 Как показано в исходном материале

Исходный пример создаёт класс без аннотаций типов. Всё работает, но при росте проекта становится сложно отличить, какие поля общие, а какие принадлежат объекту.

class Book:
    library_name = "Central Library"

    def __init__(self, title, author):
        self.title = title
        self.author = author

    def __str__(self):
        return f"{self.title} by {self.author}"


book = Book("1984", "George Orwell")
print(book)
print(book.library_name)

❌ Почему этот подход может быть не лучшим сейчас

  • Без аннотаций типов IDE и линтеры не подскажут, какие аргументы ожидаются.
  • Атрибут класса и атрибут объекта выглядят одинаково — легко перепутать.
  • При изменении через объект тихо создаётся атрибут экземпляра, что сложно отловить в большом коде.
  • Ручное написание __init__, __repr__ и сравнений увеличивает объём шаблонного кода.

✅ Рекомендуемый современный вариант

Современный Python позволяет явно разделить классовые и объектные поля, добавить типы и автоматизировать рутину.

from dataclasses import dataclass
from typing import ClassVar


@dataclass
class Book:
    library_name: ClassVar[str] = "Central Library"
    title: str
    author: str


book = Book("1984", "George Orwell")
print(book)                 # Book(title='1984', author='George Orwell')
print(book.library_name)    # Central Library

Что улучшилось:

  • ClassVar явно говорит, что library_name принадлежит классу, а не объекту.
  • Аннотации типов делают интерфейс класса понятным без чтения тела методов.
  • @dataclass автоматически генерирует __init__, __repr__ и __eq__.
  • Меньше шаблонного кода — меньше ошибок.

Когда не нужен dataclass

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

from typing import ClassVar


class Book:
    library_name: ClassVar[str] = "Central Library"

    def __init__(self, title: str, author: str) -> None:
        self.title = title
        self.author = author

    def __str__(self) -> str:
        return f"{self.title} by {self.author}"

    def __repr__(self) -> str:
        return f"Book(title={self.title!r}, author={self.author!r})"


book = Book("1984", "George Orwell")
print(book)

🕰️ Когда старый подход ещё можно встретить

В legacy-проектах и в учебных материалах часто встречаются классы без аннотаций. Главное — распознать такой код и понимать, что obj.attr = value всегда пишет в объект, а не в класс. При рефакторинге добавляйте типы постепенно: сначала __init__, затем ClassVar для классовых полей, при необходимости — @dataclass.

Проверить по документации: @dataclass и ClassVar описаны в dataclasses и typing.ClassVar. Аннотации типов — в typing и PEP 484.