Нашли ошибку?
Скачать чистый
урок 1 из 4≈ 15 минупражнения: 0 из 3

Что трогать снаружи: _ и __

Публичный интерфейс класса и внутренние атрибуты с подчёркиванием. Изменение состояния через методы с проверками и искажение имён у __x.

Python запустится при первом выполнении

Атрибут, который можно свободно менять снаружи, легко испортить: подарочной карте записать отрицательный баланс, заказу — несуществующий статус. Объект не может защитить себя, если любой код пишет в его атрибуты что угодно. Решение называется инкапсуляция (encapsulation): объект прячет своё внутреннее состояние и даёт менять его только через свои методы — а методы проверяют, что изменение допустимо.

Публичный интерфейс и внутренние атрибуты

Методы и атрибуты, которыми пользуются снаружи, образуют публичный интерфейс класса (public API). Всё остальное — внутреннее устройство. В Python нет закрытых атрибутов, как private в Java или C++, — есть соглашение: имя, которое начинается с подчёркивания, — внутренний атрибут (internal attribute). Его можно прочитать и изменить, но код снаружи так не делает: это детали, которые автор класса вправе поменять.

[ ]Баланс прячется за методами
class GiftCard:
    def __init__(self, amount):
        self._balance = amount  # внутренний атрибут

    def balance(self):
        return self._balance

    def pay(self, amount):
        if amount > self._balance:
            return False  # денег не хватает — баланс не меняется
        self._balance -= amount
        return True


card = GiftCard(500)
print(card.pay(200), card.balance())
print(card.pay(400), card.balance())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
True 300
False 300

Снаружи с картой работают только через balance() и pay(). Метод pay проверяет, хватает ли денег, — и баланс не уйдёт в минус, пока никто не лезет в _balance напрямую.

[ ]Подчёркивание — соглашение, а не запрет
card._balance = -500  # так можно, но так не делают
print(card.balance())
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
-500
[ ]Упражнение 1Пополнение карты решено

Допишите класс GiftCard: у него уже есть balance() и pay(amount). Добавьте метод top_up(amount): положительную сумму он прибавляет к балансу и возвращает True, а ноль и отрицательную сумму не принимает — баланс не меняется, метод возвращает False.

class GiftCard:
    def __init__(self, amount):
        self._balance = amount

    def balance(self):
        return self._balance

    def pay(self, amount):
        if amount > self._balance:
            return False
        self._balance -= amount
        return True

    def top_up(self, amount):
        ...

Работа через интерфейс

Код снаружи не знает, как карта хранит деньги, — и не должен. Ему хватает методов:

[ ]Упражнение 2Покупки по карте решено

С карты card на 1000 ₽ оплачивают по очереди заказы из списка orders. Сохраните в paid список результатов pay для каждого заказа (True — оплачен, False — денег не хватило), а в left — баланс после всех оплат. Используйте только методы карты.

card = GiftCard(1000)
orders = [450, 300, 400, 150]
paid = ...
left = ...

Два подчёркивания: искажение имён

Имя с двумя подчёркиваниями в начале и без двух подчёркиваний в конце — __code, но не __init__ — Python внутри класса тихо переименовывает: к нему приписывается имя класса — _Safe__code. Это называется искажение имён (name mangling):

[ ]Где на самом деле лежит __code
class Safe:
    def __init__(self, code):
        self.__code = code  # Python переименует в _Safe__code

    def opens_with(self, code):
        return code == self.__code  # внутри класса имя то же


safe = Safe("0000")
print(safe.__dict__)
print(safe.opens_with("0000"))
print(safe._Safe__code)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
{'_Safe__code': '0000'}
True
0000
[ ]Снаружи имени __code нетошибка: AttributeError
safe.__code
Ошибкасохранённый · нажмите «Выполнить», чтобы получить свой
AttributeError: 'Safe' object has no attribute '__code'

Снаружи safe.__code не найдётся, но safe._Safe__code прочитать можно — это тоже не защита. Искажение имён нужно для другого: чтобы подкласс случайно не перезаписал внутренний атрибут базового класса своим атрибутом с тем же именем (о подклассах — в модуле 5). Поэтому для внутренних атрибутов обычно хватает одного подчёркивания, а два пишут, когда важно не столкнуться с именами в подклассах.

[ ]Упражнение 3ПИН-код терминала решено

Объявите класс Terminal: Terminal("1234") хранит ПИН-код в атрибуте __pin — с двумя подчёркиваниями. Метод check(pin) возвращает True, если переданный код совпадает с сохранённым, иначе False.

class Terminal:
    def __init__(self, pin):
        ...

    def check(self, pin):
        ...

Вопрос

Что будет, если снаружи класса выполнить card._balance = 1_000_000?

Итак: публичный интерфейс — имена без подчёркивания, внутреннее — с _. Методы с проверками стерегут состояние. Но у методов есть неудобство: баланс читается вызовом balance(), а хотелось бы — как атрибут, card.balance. Как сделать атрибут, который нельзя испортить, — в следующем уроке.

Нашли ошибку?