Как в Python реализуются public, protected и private атрибуты

Ответ

В Python нет строгой инкапсуляции и модификаторов доступа (public, protected, private), как в Java или C++. Вместо этого используется соглашение об именовании для обозначения видимости атрибутов и методов.

  1. public (Публичный) Атрибуты без ведущих подчеркиваний. Они предназначены для свободного использования из любого места.

    self.name = "John"
  2. _protected (Защищенный) Атрибуты с одним ведущим подчеркиванием. Это соглашение для разработчиков, что атрибут предназначен для внутреннего использования в классе и его наследниках. Интерпретатор Python не накладывает никаких ограничений на доступ к нему.

    self._age = 30
  3. __private (Приватный) Атрибуты с двумя ведущими подчеркиваниями. Python применяет к таким именам механизм искажения имен (name mangling), преобразуя имя в _ИмяКласса__имя_атрибута. Это делает случайное обращение извне затруднительным, но не невозможным.

    self.__secret_code = "1234"

Пример:

class Person:
    def __init__(self, name, age, secret):
        self.name = name              # public
        self._age = age               # _protected
        self.__secret = secret        # __private

    def display(self):
        # Внутри класса есть доступ ко всем атрибутам
        print(f"Name: {self.name}, Age: {self._age}, Secret: {self.__secret}")

p = Person("Alice", 25, "qwerty")

# Доступ к атрибутам
print(p.name)                  # OK: 'Alice'
print(p._age)                  # OK, но не рекомендуется: 25

try:
    print(p.__secret)          # Ошибка: AttributeError
except AttributeError as e:
    print(e)

# Доступ к "приватному" атрибуту через искаженное имя
print(p._Person__secret)       # OK: 'qwerty'

Это соглашение помогает избежать случайных конфликтов имен в подклассах и сигнализирует о том, какие части класса являются его внутренним API.

Ответ 18+ 🔞

Вот же ж, народ, смотрите, какой прикол! В Python, в отличие от этих занудных Java или C++, нету этих ваших строгих модификаторов доступа, которые кричат "публично!", "защищено!", "приватно, блядь, не лезь!". Тут всё на доверии, как в хорошей коммуналке. Работает соглашение об именовании, типа негласный договор между разработчиками: "Ты не лезь туда, где подчёркивания, а я тебя не буду бить по рукам". Хотя, по факту, лезть можно куда угодно, если очень хочется.

  1. public (Публичный) Всё, что без подчёркиваний в начале — это как гостиная, заходи кто хочешь, бери что нравится. Открыто для всех.

    self.name = "Васян"
  2. _protected (Защищённый) Одно подчёркивание в начале — это как комната с табличкой "Не влезай, убьёт!". На самом деле дверь не заперта, но все понимают, что это для внутренних дел класса и его детей-наследников. Залезешь — сам дурак, если что сломается.

    self._age = 30  # Эй, это моё внутреннее дело, сколько мне лет!
  3. __private (Приватный) А вот два подчёркивания — это уже интереснее. Python, видя такое, включает хитрожопый механизм искажения имён (name mangling). Он берёт это имя и коверкает его в _ИмяКласса__имя_атрибута. Это не замок, а скорее камуфляж. Случайно найти сложно, но если знать секрет — всё твоё.

    self.__secret_code = "отвали123"  # Попробуй угадай, как теперь меня зовут!

Смотри, как это работает на живом примере:

class Person:
    def __init__(self, name, age, secret):
        self.name = name              # публично, как портрет на фасаде
        self._age = age               # защищённо, как паспорт в столе
        self.__secret = secret        # приватно, как дневник под матрасом

    def display(self):
        # Внутри своего класса я, естественно, знаю все свои тайны
        print(f"Имя: {self.name}, Возраст: {self._age}, Секрет: {self.__secret}")

p = Person("Алиса", 25, "пароль от Wi-Fi")

# Доступ к атрибутам снаружи
print(p.name)                  # Окей, без проблем: 'Алиса'
print(p._age)                  # Технически ок, но как-то некультурно: 25

try:
    print(p.__secret)          # А вот тут, ёпта, облом! AttributeError!
except AttributeError as e:
    print(e)                   # Сообщит, что атрибута '__secret' не существует. Ну конечно, ведь имя-то искажено!

# Но если ты хитрожопый и знаешь про name mangling...
print(p._Person__secret)       # Бац, и вот он, доступ! 'пароль от Wi-Fi'

Вот такая, блядь, философия. Не "запретить", а "сделать неудобно". Чтобы ты десять раз подумал, прежде чем лезть во внутреннюю кухню класса. А если всё-таки полез — то вся ответственность на тебе, когда всё ебнется.