🐛 Типичные ошибки

⚡ Частые ошибки

  • Забыть аргумент other в __add__.
  • Не возвращать NotImplemented для неподходящего типа.
  • Возвращать не int из __len__ или не bool из __bool__.
  • Изменять объект в __add__ вместо создания нового.
  • Забыть реализовать __eq__ при использовании @total_ordering.
  • Непоследовательно обрабатывать регистр ключей в контейнере.

❌ Ошибка 1: у метода сравнения/арифметики нет аргумента other

# Неправильно
class Box:
    def __init__(self, weight: float) -> None:
        self.weight = weight

    def __add__(self):
        return Box(self.weight + 1)

Почему плохо: Python передаёт в метод два аргумента — self и other. Без other вызов box1 + box2 вызовет TypeError.

✅ Исправление

class Box:
    def __init__(self, weight: float) -> None:
        self.weight = weight

    def __add__(self, other: Box) -> Box:
        if not isinstance(other, Box):
            return NotImplemented
        return Box(self.weight + other.weight)

❌ Ошибка 2: не возвращать NotImplemented

# Неправильно
class Vector:
    def __add__(self, other: Vector) -> Vector:
        return Vector(self.x + other.x, self.y + other.y)

Почему плохо: если other окажется числом или строкой, Python не сможет попробовать вызвать other.__radd__, и вы получите неинформативную ошибку.

✅ Исправление

from __future__ import annotations


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)

❌ Ошибка 3: __len__ возвращает не целое число

# Неправильно
class Notes:
    def __len__(self):
        return len(self._data)  # ок

class Broken:
    def __len__(self):
        return "10"  # TypeError!

Почему плохо: len() ожидает, что метод вернёт неотрицательное целое число. Иначе будет TypeError.

✅ Исправление

class Notes:
    def __init__(self) -> None:
        self._data: dict[str, str] = {}

    def __len__(self) -> int:
        return len(self._data)

❌ Ошибка 4: __bool__ возвращает не bool

# Неправильно
class Counter:
    def __bool__(self):
        return self.value  # может вернуть int

Почему плохо: хотя Python и приведёт результат к bool, явное возвращение bool делает намерение очевидным и предотвращает неожиданные значения.

✅ Исправление

class Counter:
    def __init__(self, value: int) -> None:
        self.value = value

    def __bool__(self) -> bool:
        return self.value > 0

❌ Ошибка 5: изменение объекта в __add__

# Неправильно
class Vector:
    def __add__(self, other: Vector) -> Vector:
        self.x += other.x
        self.y += other.y
        return self

Почему плохо: оператор + должен возвращать новый объект, как это делают числа и строки. Для изменения на месте используется __iadd__.

✅ Исправление

from __future__ import annotations


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)

❌ Ошибка 6: @total_ordering без __eq__

# Неправильно
from functools import total_ordering

@total_ordering
class Book:
    def __lt__(self, other):
        return self.title < other.title

Почему плохо: декоратору нужен __eq__ для генерации операторов неравенства. Без него поведение == и != останется стандартным (по идентификатору).

✅ Исправление

from functools import total_ordering


@total_ordering
class Book:
    def __init__(self, title: str) -> None:
        self.title = title

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

    def __lt__(self, other: "Book") -> bool:
        return self.title < other.title

❌ Ошибка 7: непоследовательная нормализация ключей

# Неправильно
class Notes:
    def __setitem__(self, key: str, value: str) -> None:
        self._data[key.lower()] = value

    def __contains__(self, key: str) -> bool:
        return key in self._data  # без lower()

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

✅ Исправление

class Notes:
    def __init__(self) -> None:
        self._data: dict[str, str] = {}

    def __setitem__(self, key: str, value: str) -> None:
        self._data[key.lower()] = value

    def __getitem__(self, key: str) -> str:
        return self._data[key.lower()]

    def __contains__(self, key: str) -> bool:
        return key.lower() in self._data