Python. Инкапсуляция
Время чтения: 4 минут
Эта статья является составной частью статьи про ООП – если ты хочешь понять всю картину, то лучше начать с неё.
Инкапсуляция – позволяет скрыть детали реализации.
Ты когда-нибудь пробовал разобрать свой телефон или ноутбук? Это очень сложный процесс, который как-будто специально сделан максимально трудоёмким для пользователя. Зачем это нужно его создателям?
Если бы это было очень просто, то ты бы мог залезть “просто посмотреть” и случайно что-то сломать в этом сложном устройстве.
Так вот на программирование это переносится идеально. Только вместо устройства здесь твои классы, а вместо пользователя другие разработчики – только эти пользователи хотят не “просто посмотреть”, а изменить твой код или унаследоваться от твоего класса.
Если ты не затруднишь доступ к чувствительным местам своих классов, то, чем сложнее твой код, тем больше вероятность что другой разработчик чего-то в нём не поймёт и сломает всё к чертям :)
В рамках этой статьи я рассмотрю инкапсуляцию – способ сокрытия переменных и методов от шаловливых ручек разработчиков.
Публичные/приватные поля/методы
Во всех примерах до этого я писал названия полей в классах вот так:
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 года)
- Два подхода к инкапсуляции в наследовании (методы для получения нужных полей или защищённое поле)
Если что – пиши, я помогу и постараюсь объяснить лучше.
Если ты ещё полон сил, то вернись к статье про ООП, и продолжи постижение ООП.