Инкапсуляция: _protected и __private
Одно подчёркивание — договорённость, два — искажение имени. Учимся прятать детали класса за публичным интерфейсом и понимать name mangling.
Редакция Питоники
Методы из прошлого урока открывают класс наружу, но у класса есть и внутренняя кухня: баланс, который нельзя менять напрямую, кэш, служебные флаги. Инкапсуляция — умение разделить публичный интерфейс (что можно всем) и детали реализации (что трогать нельзя). В Python она построена не на запретах, а на договорённостях — и это работает.
Одно подчёркивание: внутреннее по договорённости
Одна ведущая черта — _имя — сигнал коллегам: «внутренняя деталь, не трогай». Язык при этом ничего не запрещает: атрибут читается как обычный. Защита здесь социальная, и это не слабость: большинство ошибок случается не от злого умысла, а от случайного изменения. Имя _score честно говорит — работай с интерфейсом, а не с внутренностями.
class Player:
def __init__(self, name):
self.name = name
self._score = 0
def add_points(self, points):
if points > 0:
self._score += points
hero = Player("Алиса")
hero.add_points(15)
print(hero.name, hero._score)
Алиса 15
| Имя | Статус | Кто использует |
|---|---|---|
| name | публичное | все — это часть интерфейса |
| _name | внутреннее по договорённости | код самого класса и его наследников |
| __name | приватное, с искажением имени | только сам класс |
| __name__ | магическое имя языка | никто — свои имена в таком виде не придумывают |
Двойное подчёркивание и name mangling
class Account:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
acc = Account("Анна", 1000)
acc.deposit(500)
print(acc.get_balance())
print(acc.__dict__)
1500
{'owner': 'Анна', '_Account__balance': 1500}Фокус называется name mangling: внутри тела класса любое имя вида __name автоматически переименовывается в _ИмяКласса_name. Поэтому acc.__balance снаружи не существует — AttributeError, а словарь __dict__ показывает переименованное значение. Это не приватность: кто знает механику — достанет значение. Цель другой природы: такое имя невозможно случайно перекрыть в наследнике и невозможно спутать с интерфейсом. Инкапсуляция в Python — не стена, а правила приличия: язык прячет имя, но не запирает дверь.
class VendingMachine:
def __init__(self, coins):
self.__coins = coins
def buy(self, price):
if self.__coins >= price:
self.__coins -= price
print("Товар выдан")
else:
print("Недостаточно монет")
machine = VendingMachine(100)
machine.buy(40)
machine.buy(80)
Товар выдан Недостаточно монет
Инкапсуляция в Python — это культура интерфейса: публичные методы и свойства наружу, _детали и __внутренности — внутрь. Дальше: @property — элегантная замена пары get_balance() и set_balance(); наследование — где _protected-атрибуты особенно коварны: потомок видит всю внутреннюю кухню родителя.
Сначала предскажи ответ в голове — это главный навык программиста.
class Safe:
def __init__(self):
self.__code = 111
safe = Safe()
print(safe._Safe__code)
class Counter:
def __init__(self):
self.__value = 0
def bump(self):
self.__value += 2
def value(self):
return self.__value
c = Counter()
c.bump()
c.bump()
print(c.value())
class Account:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
acc = Account(500)
acc.__balance = 9999
print(acc.get_balance())
1. Что означает атрибут с одним подчёркиванием _score?
2. Что произойдёт при обращении acc.__balance извне класса Account?
3. Во что превратится self.__balance внутри класса Account?
4. Зачем классу публичные методы вроде deposit() вместо прямого доступа к балансу?
5. Защищает ли __name от доступа извне?
6. Имена вида __name__ (два подчёркивания с обеих сторон) — это...
Создайте класс PiggyBank (копилка): __init__ принимает начальную сумму и хранит её в приватном атрибуте __coins. Добавьте метод add(coin), принимающий только монеты 1, 2, 5, 10 (любую другую печать «Такой монеты нет»), и метод smash(), который возвращает накопленное и обнуляет копилку. Заведите копилку на 50, добавьте 10, попробуйте добавить 7, затем разбейте копилку и выведите итог.
Что такое инкапсуляция в Python простыми словами?
Это разделение класса на публичный интерфейс (методы, которыми пользуются все) и внутренние детали (атрибуты, которые трогать нельзя). В Python она держится на соглашениях: _имя просит не трогать, __имя дополнительно искажает имя, а все проверки живут в публичных методах.
Чем __name отличается от _name?
_name — чистая договорённость: атрибут доступен как есть, черта лишь сигнал «внутреннее». __name внутри класса переименовывается в _Класс__name (name mangling): обращение к исходному имени снаружи даёт AttributeError. Второй механизм надёжнее защищает от случайного перекрытия имён, но тоже не является настоящей приватностью.
Как по-настоящему ограничить доступ к атрибуту?
Никак — в Python принята модель «мы все согласные взрослые». Практическое решение: хранить данные в _private или __private атрибуте и выпускать наружу методы или @property с проверками. Чужой код сможет обойти защиту, но корректному коду она не даст испортить объект.
Урок 6: @propertyПочему в Python нет настоящего private, как в Java?
Философия языка: читаемость и доверие важнее запретов. Настоящая приватность мешает отладке, тестированию и наследованию, а name mangling оставили для единственной реальной проблемы — случайных коллизий имён при наследовании. От дисциплинированного разработчика в Python и не требуется решёток.
Понравился урок? Сошлитесь на него
«Инкапсуляция в Python — не стена, а правила приличия: язык прячет имя, но не запирает дверь.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
ООП · Урок 4
Методы: обычные, classmethod и staticmethod
У методов Python три лика: обычный с self, classmethod с cls и staticmethod без ничего. Строим альтернативный конструктор from_string и расставляем границы.
ООП · Урок 6
@property: вычисляемые атрибуты
@property превращает метод в атрибут без скобок: площадь пересчитывается сама, сеттер проверяет данные. Разбираем, когда свойство излишне.
ООП · Урок 7
Наследование: расширяем классы
Наследование — способ расширять классы без переписывания: потомок получает всё от родителя, переопределяет и дополняет через super(). Родство проверяет isinstance.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
NumPy · Урок 1
Что такое NumPy и как установить через pip: первый массив ndarray
Первый массив ndarray: создаём, сравниваем со списком, разбираем dtype и shape — и ускоряем сумму миллиона чисел примерно в сто раз.
что такое numpy простыми словами
NumPy · Урок 5
Broadcasting в NumPy: как складывать массивы разных размеров
Складываем вектор с матрицей без циклов: три правила broadcasting, столбцы через reshape и np.newaxis и честный разбор ошибки operands could not be broadcast together.
что такое broadcasting в numpy простыми словами
ООП · Урок 9
Полиморфизм и утиная типизация в Python
Полиморфизм — когда разные классы отвечают на один и тот же вызов каждый по-своему, а ваш код не интересуется, чей метод выполнится. Разбираем один интерфейс, утиную типизацию и функции над разнородными объектами.
полиморфизм python простыми словамиодин интерфейс много реализаций python