❌ Ошибка 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