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

⚡ Кратко

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

Ошибка 1. Забыли self при обращении к приватному атрибуту

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

    def show_title(self) -> None:
        print(__title)  # NameError: name '_Book__title' is not defined

Почему: внутри метода __title манглируется в _Book__title, но без self Python ищет глобальное имя.

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

    def show_title(self) -> None:
        print(self.__title)

Ошибка 2. Обращение к приватному атрибуту извне

book = Book("1984")
print(book.__title)  # AttributeError

Почему: имя манглировано в _Book__title, поэтому прямое обращение не работает.

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

Ошибка 3. Путаница в имени свойства

class Temperature:
    def __init__(self, value: float) -> None:
        self.__celsius = value

    @property
    def __celsius(self) -> float:  # манглированное имя, не то, что ожидалось
        return self.__celsius

t = Temperature(25)
print(t.celsius)  # AttributeError

Почему: свойство должно называться celsius, а не __celsius.

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

    @property
    def celsius(self) -> float:
        return self.__celsius

Ошибка 4. Отсутствие сеттера у @property

class Circle:
    def __init__(self, radius: float) -> None:
        self.__radius = radius

    @property
    def radius(self) -> float:
        return self.__radius


c = Circle(5)
c.radius = 10  # AttributeError: can't set attribute

Почему: если нужно изменять свойство, определите сеттер через @radius.setter.

Исправление: добавьте сеттер или сделайте свойство read-only осознанно.

Ошибка 5. Попытка переопределить приватный метод в наследнике

class Base:
    def __init__(self) -> None:
        self.__setup()

    def __setup(self) -> None:
        print("base setup")


class Child(Base):
    def __setup(self) -> None:  # не переопределяет Base.__setup
        print("child setup")


Child()  # base setup

Почему: приватный метод манглируется в _Base__setup и _Child__setup, поэтому они не связаны.

Исправление: используйте защищённый метод _setup, если нужно переопределение.

Ошибка 6. В __init__ присвоили приватное поле напрямую — сеттер не сработал

class Person:
    def __init__(self, age: int) -> None:
        self.__age = age  # проверка обойдена

    @property
    def age(self) -> int:
        return self.__age

    @age.setter
    def age(self, value: int) -> None:
        if value < 0:
            raise ValueError("Возраст не может быть отрицательным")
        self.__age = value


p = Person(-10)
print(p.age)  # -10, хотя сеттер запрещает отрицательные значения

Почему: self.__age = age пишет прямо в приватное поле, а сеттер вызывается только при обращении к свойству age.

Исправление: в __init__ писать self.age = age — тогда проверка выполнится при создании объекта.

Ошибка 7. Бесконечная рекурсия в дескрипторе

class Age:
    def __set__(self, instance, value: int) -> None:
        instance.age = value  # снова вызывает Age.__set__ → рекурсия


class Person:
    age = Age()

    def __init__(self, age: int) -> None:
        self.age = age


Person(25)  # RecursionError: maximum recursion depth exceeded

Почему: instance.age — это тот самый атрибут, которым управляет дескриптор, поэтому присваивание снова попадает в __set__.

Исправление: хранить значение под другим именем: instance._age = value или setattr(instance, self.private_name, value) при использовании __set_name__.

Ошибка 8. Имя дескриптора не совпадает с именем атрибута

class PositiveNumber:
    def __set_name__(self, owner, name: str) -> None:
        self.private_name = "_" + name

    def __set__(self, instance, value: float) -> None:
        if value < 0:
            raise ValueError("Значение не может быть отрицательным")
        setattr(instance, self.private_name, value)


class Person:
    balance = PositiveNumber()

    def __init__(self, balance: float) -> None:
        self.money = balance  # ❌ атрибут называется иначе


p = Person(-100)
print(p.money)  # -100 — дескриптор не участвовал

Почему: дескриптор срабатывает только при обращении к атрибуту с тем же именем, под которым он объявлен в классе (balance).

Исправление: присваивать self.balance = balance.