Инкапсуляция и приватные атрибуты в Python
В этом руководстве вы узнаете об инкапсуляции и о том, как использовать приватные атрибуты для выполнения инкапсуляции в Python.
- Что такое инкапсуляция в Python?
- Пример инкапсуляции в Python
- Приватные атрибуты
- Искажение имени двойным подчеркиванием
Что такое инкапсуляция в Python?
Существует четыре фундаментальные концепции объектно-ориентированного программирования: абстракция, инкапсуляция, наследование и полиморфизм.
Инкапсуляция в Python— это пакет данных и функций, которые работают с этими данными, в одном объекте. Тем самым вы сможете скрыть внутреннее состояние объекта снаружи. Это известно как сокрытие информации.
Класс является примером инкапсуляции. Класс объединяет данные и методы в один блок и предоставляет доступ к своим атрибутам через методы.
Идея сокрытия информации заключается в том, что если у вас есть атрибут, который не виден снаружи, вы можете контролировать доступ к его значению, чтобы убедиться, что ваш объект всегда имеет допустимое состояние.
Давайте рассмотрим пример, чтобы лучше понять концепцию инкапсуляции.
Пример инкапсуляции в Python
Следующий код определяет класс Counter:
class Counter: def __init__(self): self.current = 0 def increment(self): self.current += 1 def value(self): return self.current def reset(self): self.current = 0
Класс Counter имеет один атрибут под названием current, который по умолчанию равен нулю. И у него есть три метода:
- increment() увеличивает значение текущего атрибута на единицу;
- value() возвращает текущее значение текущего атрибута;
- reset() устанавливает значение текущего атрибута в ноль.
Следующий пример создает новый экземпляр класса Counter и трижды вызывает метод increment(), прежде чем отобразить текущее значение Counter на экране:
counter = Counter() counter.increment() counter.increment() counter.increment() print(counter.value())
Выход:
3
Он работает отлично, но есть одна проблема.
Извне класса Counter вы по-прежнему можете получить доступ к текущему атрибуту и изменить его на все, что захотите. Например:
counter = Counter() counter.increment() counter.increment() counter.current = -999 print(counter.value())
Выход:
-999
В этом примере мы создаем экземпляр класса Counter, дважды вызываем метод increment() и устанавливаем для текущего атрибута недопустимое значение -999.
Так как же предотвратить изменение текущего атрибута за пределами класса Counter?
Для этого в игру вступают приватные атрибуты.
Приватные атрибуты
Приватные атрибуты могут быть доступны только из методов класса. Другими словами, они не могут быть доступны извне класса. В Python нет концепции приватных атрибутов. Другими словами, все атрибуты доступны снаружи класса.
По соглашению вы можете определить приватный атрибут, добавив к нему одиночное подчеркивание(_):
_attribute
Это означает, что атрибутом _attribute нельзя манипулировать, и в будущем он может иметь серьезные изменения.
Следующее по соглашению переопределяет класс Counter с текущим атрибутом в качестве закрытого:
class Counter: def __init__(self): self._current = 0 def increment(self): self._current += 1 def value(self): return self._current def reset(self): self._current = 0
Искажение имени двойным подчеркиванием
Если вы добавите к имени атрибута двойное подчеркивание(__), например:
__attribute
Python автоматически изменит имя __attribute на:
_class__attribute
В Python это называется name mangling (искажением имен).
Сделав это, вы не сможете получить доступ к __attribute напрямую извне класса, например:
instance.__attribute
Однако вы все равно можете получить к нему доступ, используя имя _class__attribute:
instance._class__attribute
В следующем примере класс Counter переопределяется с атрибутом __current:
class Counter: def __init__(self): self.__current = 0 def increment(self): self.__current += 1 def value(self): return self.__current def reset(self): self.__current = 0
Теперь, если вы попытаетесь получить доступ к атрибуту __current, вы получите сообщение об ошибке:
counter = Counter() print(counter.__current)
Выход:
AttributeError: 'Counter' object has no attribute '__current'
Однако вы можете получить доступ к атрибуту __current как _Counter___current следующим образом:
counter = Counter() print(counter._Counter__current)