Инкапсуляция по‑питоновски и @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для инвариантов и нормализации: читается как поле, работает как метод. - Геттер/сеттер — там, где нужно контролировать ввод; только геттер — для вычисляемых значений.
- Свойства позволяют менять внутреннее устройство класса, не ломая внешний интерфейс.