Дескрипторы
__get__/__set__ — основа property, methods, ORM полей.
Дескриптор — объект-посредник для атрибута
Дескриптор — это любой объект с методами __get__, __set__ (или __delete__), помещённый в класс как атрибут. Когда ты обращаешься к этому атрибуту через экземпляр, Python вызывает соответствующий метод дескриптора. Так работают property, методы, classmethod, staticmethod, поля ORM (Django, SQLAlchemy) — это всё дескрипторы под капотом.
Как читается
class Positive:
def __set_name__(self, owner, name):
self.name = name # запоминаем имя атрибута
def __get__(self, inst, owner):
return inst.__dict__[self.name]
def __set__(self, inst, value):
if value <= 0:
raise ValueError("must be > 0")
inst.__dict__[self.name] = value
class Box:
width = Positive()
__set_name__ (3.6+) даёт дескриптору знать своё имя — это позволяет хранить значение в inst.__dict__[name] и иметь по одному значению на экземпляр.
Пример: lazy-атрибут с кешем
class cached_property:
def __init__(self, fn): self.fn = fn
def __set_name__(self, o, n): self.name = n
def __get__(self, inst, owner):
if inst is None: return self
v = self.fn(inst)
inst.__dict__[self.name] = v # перекрывает дескриптор
return v
class Report:
@cached_property
def heavy(self):
print("compute")
return 42
r = Report()
r.heavy # compute → 42
r.heavy # 42 (из __dict__, без вычисления)
Когда использовать / подводные камни
👍 Валидация полей, ленивые вычисления, прозрачные обёртки (логгирование доступа), поля ORM/dataclass-аналогов.
👎 Не клади значение в сам дескриптор (self.value = ...) — он один на класс, и все экземпляры начнут делить его. Всегда храни в inst.__dict__ или в WeakKeyDictionary. Для простых случаев @property короче и читаемее.