Перейти к основному содержимому

Инкапсуляция по‑питоновски и @property

Инкапсуляция — это про «аккуратный доступ к данным»: объект сам решает, как хранит значения и какие проверки делает, а внешний код пользуется удобными полями/методами. В Python нет жёстких модификаторов public/private, вместо этого действуют понятные соглашения и инструменты языка: подчёркивания в именах, name mangling и свойства @property.

Соглашения доступа: публичное, «внутреннее» и скрытое имя

Публичные поля и методы называются как обычно. Одно подчёркивание говорит: «для внутреннего пользования». Два подчёркивания включают «перепаковку имени» (name mangling) — защита от случайного доступа и конфликтов имён в наследовании.

class HockeyPlayer:
def __init__(self, name):
self.name = name # публично
self._age = 17 # «внутреннее» по соглашению
self.__number = 10 # скрытое имя (name mangling)


p = HockeyPlayer('Иван')
print(p.name)
print(p._age) # технически доступно, но так делать не рекомендуют

# Прямой доступ к двойному подчёркиванию не сработает
try:
print(p.__number)
except AttributeError as e:
print('Ошибка:', e)

# Но атрибут существует под перепакованным именем
print(hasattr(p, '_HockeyPlayer__number'))
print(p._HockeyPlayer__number)
Иван
17
Ошибка: 'HockeyPlayer' object has no attribute '__number'
True
10

Вывод: одно подчёркивание — мягкое предупреждение «не трогай напрямую», два подчёркивания — техническая защита от случайного доступа. Для осознанного управления доступом используем @property.

Свойства @property: читаются как поле, работают как метод

Свойство позволяет читать значение как обычный атрибут, но под капотом запускается код: можно валидировать, форматировать, лениво вычислять. У свойства могут быть геттер (чтение), сеттер (запись) и делитер (удаление).

Пример: поле age с проверкой и вычисляемое поле full_name (только для чтения).

class HockeyPlayer:
def __init__(self, name, surname, age):
self.name = name
self.surname = surname
self._age = age # внутреннее хранилище

@property
def age(self): # чтение: p.age
return self._age

@age.setter
def age(self, value): # запись: p.age = ...
if value < 0:
raise ValueError('Возраст не может быть отрицательным')
self._age = value

@property
def full_name(self): # вычисляемое, только для чтения
return f'{self.name} {self.surname}'


p = HockeyPlayer('Иван', 'Иванов', 17)
print(p.full_name) # читается как поле
print(p.age)
p.age = 18 # проходит проверку
print(p.age)

try:
p.age = -1 # не проходит проверку
except ValueError as e:
print('Ошибка:', e)
Иван Иванов
17
18
Ошибка: Возраст не может быть отрицательным

Преимущество свойства: можно изменить внутреннее хранилище (например, переименовать _age в _years) без ломки внешнего интерфейса — внешний код по‑прежнему использует obj.age.

Нормализация данных через сеттер

Сеттер удобно использовать для приведения входа к единому формату: обрезать пробелы, фиксировать регистр, заменять пустые значения на осмысленные.

class HockeyPlayer:
def __init__(self, name, team):
self.name = name
self._team = None
self.team = team # пойдёт через сеттер

@property
def team(self):
return self._team

@team.setter
def team(self, value):
cleaned = (value or '').strip()
self._team = cleaned if cleaned else 'Без команды'


p = HockeyPlayer('Иван', ' Метеор ')
print(p.team)
p.team = ' '
print(p.team)
Метеор
Без команды

Читаемое логическое свойство (только геттер)

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

class HockeyPlayer:
def __init__(self, name, age):
self.name = name
self._age = age

@property
def age(self):
return self._age

@age.setter
def age(self, v):
if v < 0:
raise ValueError('Возраст не может быть отрицательным')
self._age = v

@property
def is_adult(self):
return self._age >= 18


p = HockeyPlayer('Иван', 17)
print(p.is_adult)
p.age = 18
print(p.is_adult)
False
True

Короткая памятка

  • Одно подчёркивание (_age) — «внутреннее» поле по соглашению; два (__age) — скрытие имени (name mangling).
  • Используйте @property для инвариантов и нормализации: читается как поле, работает как метод.
  • Геттер/сеттер — там, где нужно контролировать ввод; только геттер — для вычисляемых значений.
  • Свойства позволяют менять внутреннее устройство класса, не ломая внешний интерфейс.