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 предлагает выражать инкапсуляцию.