💻 Примеры: инкапсуляция в действии
⚡ Кратко
Сначала запустите ключевые примеры без изменений, затем поменяйте входные данные и объясните получившийся результат.
Пример 1. Публичные, защищённые и приватные атрибуты
class Book:
def __init__(self, title: str, author: str) -> None:
self.title = title # публичный
self._author = author # защищённый
self.__notes = [] # приватный
def add_note(self, note: str) -> None:
self.__notes.append(note)
def show_notes(self) -> None:
for note in self.__notes:
print(f"- {note}")
book = Book("1984", "George Orwell")
print(book.title) # 1984
book.title = "Animal Farm"
print(book._author) # доступ возможен, но нежелателен
book.add_note("Первая глава прочитана")
book.show_notes()
# print(book.__notes) # AttributeError
Приватный список __notes доступен только через методы класса.
Пример 2. Защищённый метод для внутренней логики
class Report:
def __init__(self, data: list[str]) -> None:
self.data = data
def generate(self) -> None:
cleaned = self._prepare_data()
print("Отчёт:")
print("\n".join(cleaned))
def _prepare_data(self) -> list[str]:
return [line.strip() for line in self.data if line.strip()]
class SalesReport(Report):
def _prepare_data(self) -> list[str]:
raw = super()._prepare_data()
return [line for line in raw if not line.startswith("#")]
data = [
" Продажи за январь ",
"",
" # внутренний комментарий ",
" Доход: $5000 "
]
report = SalesReport(data)
report.generate()
Метод _prepare_data — защищённый. Его можно переопределить в наследнике, но внешний код пользуется только generate().
Пример 3. Геттеры и сеттеры
class Temperature:
def __init__(self, value: float) -> None:
self.__validate_celsius(value)
self.__celsius = value
def get_celsius(self) -> float:
return self.__celsius
def set_celsius(self, value: float) -> None:
self.__validate_celsius(value)
self.__celsius = value
@staticmethod
def __validate_celsius(value: float) -> None:
if value < -273.15:
raise ValueError("Температура не может быть ниже абсолютного нуля")
temp = Temperature(20)
print(temp.get_celsius()) # 20
temp.set_celsius(-300) # ValueError
Валидация вынесена в приватный статический метод, чтобы не дублировать код.
Пример 4. Свойства через @property
class SecureUSB:
def __init__(self, data: str, password: str) -> None:
self.__data = data
self.__password = password
self.__locked = True
def unlock(self, password: str) -> bool:
if password == self.__password:
self.__locked = False
return True
return False
def lock(self) -> None:
self.__locked = True
@property
def data(self) -> str:
if self.__locked:
raise PermissionError("Access denied")
return self.__data
usb = SecureUSB("Secret plans", "1234")
print(usb.unlock("wrong")) # False
try:
print(usb.data)
except PermissionError as e:
print(e) # Access denied
usb.unlock("1234")
print(f"Data: {usb.data}") # Data: Secret plans
usb.lock()
Свойство data выглядит как атрибут, но внутри выполняет проверку доступа.
Пример 5. Read-only свойство
class Circle:
def __init__(self, radius: float) -> None:
if radius <= 0:
raise ValueError("Радиус должен быть положительным")
self.__radius = radius
@property
def radius(self) -> float:
return self.__radius
@property
def area(self) -> float:
return 3.14159 * self.__radius ** 2
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.53975
# c.radius = 10 # AttributeError
Пример 6. Проверка срабатывает уже при создании объекта
Разница между «сеттер есть» и «сеттер работает» — в том, как значение попадает в объект впервые.
class PersonUnsafe:
def __init__(self, age: int) -> None:
self.__age = age # проверка пропущена
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, value: int) -> None:
if value < 0:
raise ValueError("Возраст не может быть отрицательным")
self.__age = value
class PersonSafe:
def __init__(self, age: int) -> None:
self.age = age # значение идёт через сеттер
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, value: int) -> None:
if value < 0:
raise ValueError("Возраст не может быть отрицательным")
self.__age = value
bad = PersonUnsafe(-10)
print(bad.age) # -10 — объект с некорректными данными
# PersonSafe(-10) # ValueError: Возраст не может быть отрицательным
Пример 7. Универсальный дескриптор-валидатор
Одна проверка описана один раз и обслуживает сразу два поля.
class PositiveNumber:
def __set_name__(self, owner, name: str) -> None:
self.private_name = "_" + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.private_name)
def __set__(self, instance, value: float) -> None:
if value < 0:
raise ValueError("Значение не может быть отрицательным")
setattr(instance, self.private_name, value)
def __delete__(self, instance) -> None:
delattr(instance, self.private_name)
class Person:
age = PositiveNumber()
balance = PositiveNumber()
def __init__(self, name: str, age: int, balance: float = 0) -> None:
self.name = name
self.age = age
self.balance = balance
person = Person("Viktor", 48, 1000)
print(person.age) # 48
print(person.balance) # 1000
print(person.__dict__) # {'name': 'Viktor', '_age': 48, '_balance': 1000}
person.balance = 1500
print(person.balance) # 1500
# person.age = -10 # ValueError: Значение не может быть отрицательным
Значения лежат в _age и _balance, а age и balance — дескрипторы, управляющие доступом.