Какие механизмы инкапсуляции существуют в Python?

«Какие механизмы инкапсуляции существуют в Python?» — вопрос из категории ООП, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Python инкапсуляция — это в первую очередь соглашение, а не строгое правило, как в Java или C++. Она реализуется с помощью следующих механизмов:

  1. Соглашения об именовании (префиксы _ и __):

    • Одинарное подчеркивание (_): Сигнализирует, что атрибут или метод является защищенным (protected) и предназначен для внутреннего использования в классе или его наследниках. Технически доступ к нему не ограничен.
      class MyClass:
        def __init__(self):
            self._internal_var = 10
    • Двойное подчеркивание (__): Делает атрибут или метод приватным (private) с помощью механизма Name Mangling. Python изменяет имя атрибута, добавляя к нему имя класса (_ClassName__attribute), что затрудняет случайный доступ извне.

      class MyClass:
        def __init__(self):
            self.__private_var = 20
      
      obj = MyClass()
      # print(obj.__private_var)  # AttributeError
      print(obj._MyClass__private_var) # Так доступ возможен
  2. Свойства (@property): Декоратор @property позволяет управлять доступом к атрибутам класса, превращая методы в атрибуты, доступные только для чтения. С помощью декораторов @<name>.setter и @<name>.deleter можно добавить логику для записи и удаления, например, для валидации данных.

    Пример с валидацией:

    class BankAccount:
        def __init__(self, initial_balance):
            if initial_balance < 0:
                raise ValueError("Начальный баланс не может быть отрицательным")
            self.__balance = initial_balance
    
        @property
        def balance(self):
            """Геттер для получения баланса."""
            return self.__balance
    
        @balance.setter
        def balance(self, value):
            """Сеттер с валидацией для изменения баланса."""
            if value < 0:
                raise ValueError("Баланс не может быть отрицательным")
            self.__balance = value
    
    account = BankAccount(100)
    print(account.balance)  # 100 (работает геттер)
    account.balance = 200     # OK (работает сеттер)
    # account.balance = -50   # Вызовет ValueError

    Такой подход позволяет скрыть внутреннюю реализацию (__balance) и предоставить безопасный публичный интерфейс (balance).