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

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

Старый/исходный подход: работает, но не содержит аннотаций типов, реже использует guard clauses и может не возвращать NotImplemented.

Новый подход: type hints, f-strings, guard clauses, NotImplemented и dataclasses где уместно. Код становится самодокументируемым.

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

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

Исходные примеры корректны, но не содержат аннотаций типов и не всегда защищают методы сравнения через isinstance:

class Book:
    def __init__(self, title):
        self.title = title

    def __eq__(self, other):
        if not isinstance(other, Book):
            return NotImplemented
        return self.title == other.title

    def __lt__(self, other):
        return self.title < other.title

    def __repr__(self):
        return f"Book(title={self.title})"


class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

    def __str__(self):
        return f"({self.x}, {self.y})"

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

❌ Почему этот подход можно улучшить

  • Нет аннотаций типов. Неясно, что title — строка, а x, y — числа.
  • Меньше защиты. В __lt__ нет проверки типа other; при сравнении с неожиданным типом может возникнуть непонятная ошибка.
  • Нет guard clauses. В некоторых исходных примерах используются лишние else после return.
  • Нет современных инструментов. Например, dataclasses могли бы убрать шаблонный __init__ и __repr__.

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

Та же логика, но с type hints, f-strings, защитой типов и dataclasses:

from __future__ import annotations
from dataclasses import dataclass
from functools import total_ordering


@total_ordering
@dataclass
class Book:
    title: str

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Book):
            return NotImplemented
        return self.title == other.title

    def __lt__(self, other: Book) -> bool:
        if not isinstance(other, Book):
            return NotImplemented
        return self.title < other.title


class Vector:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

    def __add__(self, other: Vector) -> Vector:
        if not isinstance(other, Vector):
            return NotImplemented
        return Vector(self.x + other.x, self.y + other.y)

    def __repr__(self) -> str:
        return f"Vector({self.x}, {self.y})"
Преимущества:
  • Аннотации типов сразу показывают контракт метода.
  • Проверка isinstance во всех методах защищает от неожиданных типов.
  • dataclass убирает boilerplate для __init__ и __repr__.
  • @total_ordering сокращает код сравнений.
Проверить по документации: рекомендации по стилю в PEP 8, по аннотациям типов — PEP 484, о dataclasses — dataclasses.

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

Код без type hints и с ручным __init__ часто встречается в учебных материалах, тестовых скриптах и legacy-проектах, написанных до Python 3.6. Если вы видите такой код, не спешите всё переписывать: сначала убедитесь, что логика корректна, а затем постепенно добавляйте типизацию и тесты.