2025, Dec 25 18:03

Двойные подчёркивания и name mangling в Python: как избежать AttributeError

Как работает name mangling в Python: почему доступ к __attr даёт AttributeError, как читать _Class__attr корректно и когда лучше опираться на публичный API.

Попытка обратиться к атрибутам с двойным подчёркиванием между несвязанными классами в Python может казаться заманчивой, но механика искажения имён (name mangling) тут помешает. Если один класс хранит экземпляр другого и вы пытаетесь залезть в его закрытые поля, получите AttributeError. Ниже — короткое и прикладное разъяснение, что происходит и как корректно это починить, когда без этого действительно никак.

Подготовка

У нас два публичных класса. Первый отдаёт свою длину через dunder‑метод и хранит значение в атрибуте с двойным подчёркиванием. Второй принимает экземпляр первого и пытается скопировать его внутреннее значение в собственное приватное поле.

Проблемный код

class Alpha:

    def __init__(self) -> None:
        self.__count: ...

    def __len__(self) -> int:
        return self.__count


class Beta:

    def __init__(self, source: Alpha) -> None:
        self.__source = source
        self.__count = __source.__count

    def __len__(self) -> int:
        return self.__count

Выглядит просто, но в рантайме падает с AttributeError из‑за искажения имён.

Почему возникает AttributeError

Python применяет искажение имён к идентификаторам, которые начинаются с двух подчёркиваний внутри тела класса. Шаблон предсказуем: __attr превращается в _ClassName__attr. Это сделано, чтобы избежать случайных переопределений в подклассах и обозначить, что такие атрибуты предназначены для внутреннего использования.

В примере __source внутри Beta искажается до _Beta__source, а __count внутри Alpha — до _Alpha__count. Поэтому попытка обратиться к __source.__count внутри Beta на самом деле не ссылается на тот параметр, о котором вы думаете; вы не обращаетесь ни к локальному, ни к публичному атрибуту. В итоге вы тычетесь в искажённое имя атрибута, которого нет у экземпляра Alpha, и это приводит к ошибке.

Здесь нет отношений родитель—потомок или вложенности. Beta просто получает объект совершенно другого класса. Лезть в атрибуты с двойным подчёркиванием этого объекта — это не «защищённый» доступ и не что‑то особенное; это попытка тронуть приватную деталь реализации другого класса.

Решение: обращаться к искажённому имени на нужном классе

Если вам действительно нужно прочитать этот приватный атрибут, укажите его ровно так, как Python искажает его для класса‑владельца. Исправление — заменить неверный доступ на корректно искажённое имя атрибута Alpha.

class Alpha:

    def __init__(self) -> None:
        self.__count: ...

    def __len__(self) -> int:
        return self.__count


class Beta:

    def __init__(self, source: Alpha) -> None:
        self.__source = source
        self.__count = source._Alpha__count  # корректное обращение к искажённому имени

    def __len__(self) -> int:
        return self.__count

Так вы явно обращаетесь к приватному полю Alpha по имени, которое для него генерирует Python, и избегаете AttributeError.

Почему это важно понимать

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

Более того, искажение имён может вовсе не понадобиться. Если цель — получить размер, вызов len на хранимом экземпляре не нарушает инкапсуляцию и сохраняет данные «живыми». Например, вычисляя длину по требованию в классе‑потребителе, вы гарантируете, что она отражает изменения, если внутренний экземпляр обновится. Ключевой момент неизменен: приватные/защищённые атрибуты стоит использовать только внутри класса, которому они принадлежат.

Выводы

Используйте двойные подчёркивания только тогда, когда хотите жёстко спрятать детали реализации внутри класса и готовы принять ограничения, связанные с искажением имён. Когда действительно необходимо обратиться к приватному полю из другого класса, указывайте корректно искажённое имя на классе‑владельце, как показано выше. Но прежде чем прибегать к этому обходному пути, подумайте, не должен ли атрибут быть публичным — или можно опереться на публичный API вроде len(obj). Такой подход избавляет от хрупкого, тесно связанного кода и соответствует тому, как Python предлагает выражать инкапсуляцию.