+--------------------------+
|.------------------------.|
|| kee_reel@blog:~$ cd    ||
|| си python терминал     ||
|| opengl sql             ||
||                        ||
||                обо_мне ||
|.------------------------.|
+-::--------------------::-+
.--------------------------.
 // /ooooooooooooooooooooo\\ \\ 
 // /ooooooooooooooooooooooo\\ \\ 
//------------------------------\\
\\------------------------------//

Python. Инкапсуляция

Эта статья является составной частью статьи про ООП – если ты хочешь понять всю картину, то лучше начать с неё.

Инкапсуляция – позволяет скрыть детали реализации.

Ты когда-нибудь пробовал разобрать свой телефон или ноутбук? Это очень сложный процесс, который как-будто специально сделан максимально трудоёмким для пользователя. Зачем это нужно его создателям?

Разбор ноутбука

Если бы это было очень просто, то ты бы мог залезть “просто посмотреть” и случайно что-то сломать в этом сложном устройстве.

Так вот на программирование это переносится идеально. Только вместо устройства здесь твои классы, а вместо пользователя другие разработчики – только эти пользователи хотят не “просто посмотреть”, а изменить твой код или унаследоваться от твоего класса.

Если ты не затруднишь доступ к чувствительным местам своих классов, то, чем сложнее твой код, тем больше вероятность что другой разработчик чего-то в нём не поймёт и сломает всё к чертям :)

В рамках этой статьи я рассмотрю инкапсуляцию – способ сокрытия переменных и методов от шаловливых ручек разработчиков.

Публичные/приватные поля/методы

Во всех примерах до этого я писал названия полей в классах вот так:

class Laptop:
    def __init__(self):
        self.battery_percent = 100

Поле battery_percent – публичное, то есть любой может прочитать из него значение или записать туда что-то:

laptop = Laptop()
print(laptop.battery_percent) # Вывод: 100
laptop.battery_percent = 123
print(laptop.battery_percent) # Вывод: 123

Вот сейчас пользователь взял и поменял процент зарядки в твоём “устройстве”. Если бы у тебя в этом классе было много логики завязанной на процент зарядки, то наверняка что-то бы отвалилось.

Как защитить поле battery_percent? Сделать его приватным!

class Laptop:
    def __init__(self):
        self.__battery_percent = 100

Если писать имена переменных с “__”, то они будут приватными – то есть к ним нельзя будет обратиться вне класса.

laptop = Laptop()
print(laptop.battery_percent) # ОШИБКА: AttributeError: 'Laptop' object has no attribute 'battery_percent'

Ок, как тогда к нему получить доступ? Сделать метод, который будет возвращать значение поля __battery_percent:

class Laptop:
    def __init__(self):
        self.__battery_percent = 100

    def get_battery_percent(self):
        return self.__battery_percent
laptop = Laptop()
print(laptop.get_battery_percent()) # Вывод: 100

Ура, теперь наша логика под защитой!

Ещё ты можешь делать приватные методы, если не хочешь чтобы кто-то вызывал этот метод кроме тебя:

from time import time
class Laptop:
    def __init__(self):
        self.__start_time = self.__last_update_time = time()
        self.__battery_percent = 100
        self.__battery_consumption_per_second = 0.05
        self.__screen_brightness = 100

    def get_battery_percent(self):
        return self.__battery_percent

    # Этот метод будет периодически вызываться извне
    def update(self):
        seconds_since_last_update = time() - self.__last_update_time
        self.__battery_percent -= self.__battery_consumption_per_second * seconds_since_last_update
        self.__last_update_time = time()
        self.__update_screen_brightness()

    # А вот этот метод не хочется показывать наружу
    def __update_screen_brightness(self):
        if self.__battery_percent < 30:
            self.__screen_brightness = 50

Функция time() из библиотеки time возвращает текущее количество секунд с 1 января 1970 года – например моё текущее время 1633025404 (30 сен 2021 21:10:04 MSK). Это также называется Unix-время – можно почитать про это на вики. Все информационные системы используют именно такую запись для хранения времени.

Приватные поля в наследовании

Если ты создашь приватное поле, то доступ к нему не смогут получить ни где-то снаружи, ни в классах наследниках:

class A:
    def __init__(self):
        self.__private_field = 42

class B(A):
    def __init__(self):
        A.__init__(self)
        print(self.__private_field) # ОШИБКА: AttributeError: 'B' object has no attribute '_B__private_field'

Что делать, если доступ к полю всё же нужен? Есть два пути:

  • Добавление метода
class A:
    def __init__(self):
        self.__private_field = 0
    def get_private_field(self):
        return self.__private_field

class B(A):
    def __init__(self):
        A.__init__(self)
        print(self.get_private_field()) # Вывод: 42

То есть объект класса B как бы у себя изнутри забирает это значение.

Приватное поле

  • Смена приватного поля на защищённое (protected)

Можно написать не два подчёркивания перед названием переменной, а одно:

class A:
    def __init__(self):
        self._private_field = 0
    def get_private_field(self):
        return self._private_field

class B(A):
    def __init__(self):
        A.__init__(self)
        print(self._private_field) # Вывод: 42

Оно ничем не отличается от поля, которое было вообще без подчёркиваний – все смогут обращаться к такому полю снаружи.

Единственное что меняется – таким образом ты помечаешь его как внутреннее, и сигнализируешь другим разработчикам не трогать его.

Таким образом, ты можешь:

  • Сделать приватным и защититься от всех, но написать дополнительные методы
  • Пометить как внутреннее и сэкономить на методах, но повысить шанс возникновения проблем

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

Заключение

Итого, мы изучили:

  • Инкапсуляция (сокрытие деталей реализации)
  • Приватное поле (с двумя подчёркиваниями, никто не дотянется)
  • Приватный метод
  • Функция time из библиотеки time (возвращает количество секунд с 1 января 1970 года)
  • Два подхода к инкапсуляции в наследовании (методы для получения нужных полей или защищённое поле)

Если что – пиши, я помогу и постараюсь объяснить лучше.

Если ты ещё полон сил, то вернись к статье про ООП, и продолжи постижение ООП.