Дескрипторы и property
Как перехватить чтение и запись атрибута: дескрипторы вручную и property как готовое решение.
Дескриптор — объект, определяющий методы
__get__и/или__set__, через которые класс управляет доступом к своему атрибуту.
property: управляемый атрибут
Часто нужно, чтобы при обращении к атрибуту что-то вычислялось или проверялось, но снаружи это выглядело как обычное поле. Декоратор property превращает метод в «вычисляемый атрибут» — его читают без скобок.
class Celsius:
def __init__(self, temp):
self._temp = temp
@property
def fahrenheit(self):
return self._temp * 9 / 5 + 32
c = Celsius(100)
print("По Фаренгейту:", c.fahrenheit) # без скобок!
c._temp = 0
print("По Фаренгейту:", c.fahrenheit)
Вывод:
По Фаренгейту: 212.0 По Фаренгейту: 32.0
Снаружи c.fahrenheit выглядит как поле, но на самом деле каждый раз вызывается метод и пересчитывает значение. Это позволяет добавить логику, не меняя интерфейс класса.
Дескриптор с валидацией
property привязан к одному классу. Если одну и ту же логику (например, «значение должно быть неотрицательным») нужно переиспользовать для многих атрибутов и классов, пишут полноценный дескриптор. Метод __set_name__ автоматически узнаёт имя атрибута, __get__ перехватывает чтение, __set__ — запись.
class Positive:
def __set_name__(self, owner, name):
self.storage = "_" + name # куда складывать значение
def __get__(self, obj, objtype=None):
return getattr(obj, self.storage)
def __set__(self, obj, value):
if value < 0:
raise ValueError("должно быть >= 0")
setattr(obj, self.storage, value)
class Account:
balance = Positive() # дескриптор как атрибут класса
def __init__(self, balance):
self.balance = balance # пойдёт через __set__
a = Account(100)
print("Баланс:", a.balance)
try:
a.balance = -50
except ValueError as e:
print("Ошибка:", e)
print("Баланс не изменился:", a.balance)
Вывод:
Баланс: 100 Ошибка: должно быть >= 0 Баланс не изменился: 100
Дескриптор Positive можно повесить на любое число атрибутов в любых классах — вся валидация описана один раз. Это и есть преимущество перед property: переиспользование логики доступа.
property — это тоже дескриптор
Под капотом property реализован как дескриптор: у него есть __get__ и __set__. Разница в удобстве: property привязывает геттер/сеттер к конкретному классу, а собственный дескриптор — переиспользуемый объект для многих полей.
| Когда | Выбор |
| один вычисляемый атрибут в одном классе | property |
| одна логика доступа для многих атрибутов/классов | свой дескриптор |
Итог
propertyделает метод вычисляемым атрибутом — читается без скобок, можно добавить логику без смены интерфейса.- Дескриптор (
__get__/__set__/__set_name__) перехватывает чтение и запись атрибута. - Дескриптор переиспользуем для многих полей;
property— для одного атрибута одного класса.