Технически дескриптор — любой объект, у которого определён хотя бы один из методов протокола дескрипторов:
__get__(self, instance, owner) — вызывается при чтении obj.attr;
__set__(self, instance, value) — при присваивании obj.attr = value;
__delete__(self, instance) — при del obj.attr.
Когда такой объект лежит как атрибут класса, доступ к этому имени (obj.attr) идёт через методы дескриптора, а не напрямую к __dict__ экземпляра.
Формулировка для собеседования
Дескрипторы — это объекты с методами __get__, __set__, __delete__, которые управляют тем, как читаются, записываются и удаляются атрибуты у других объектов. В коде это выглядит следующим образом:
class LoggedAttribute:
def __init__(self, name: str):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
value = instance.__dict__.get(self.name)
print(f"GET {self.name} -> {value}")
return value
def __set__(self, instance, value):
print(f"SET {self.name} = {value}")
instance.__dict__[self.name] = value
class User:
age = LoggedAttribute("age") # дескриптор – атрибут класса
u = User()
u.age = 30 # вызывает LoggedAttribute.__set__(...)
print(u.age) # вызывает LoggedAttribute.__get__(...)Здесь age — дескриптор, который логирует чтение/запись, когда ты обращаешься к u.age.
Зачем они нужны?
На дескрипторах внутри Python построены:
@property — это частный случай дескриптора c __get__/__set__;
методы класса — функции тоже являются дескрипторами и при доступе через экземпляр превращаются в «связанный метод» с self;
@classmethod, @staticmethod — тоже реализованы через дескрипторы.
Пользовательские дескрипторы используют для:
валидации и тип-проверки полей (например,
PositiveInt,EmailStrи т.п.);ленивых вычислений и кеширования (
LazyProperty, кэширование результата первого вычисления);логирования, трассировки, контроля доступа.
Data и non-data дескрипторы
Есть два важных подтипа дескрипторов
Data descriptor — у дескриптора есть __set__ или __delete__ (обычно оба). Такие дескрипторы имеют приоритет над instance.__dict__.
Non-data descriptor — у дескриптора только __get__. В этом случае, если в instance.__dict__ есть одноимённый атрибут, он перебивает дескриптор.
Это объясняет, почему property - это «жёсткое» поле: оно всегда контролирует и чтение, и запись, и не даёт тебе просто так сохранить поверх него другое значение в экземпляре. Любая попытка obj.prop = ... идёт через его __set__.
Обычный метод — «мягкий» дескриптор: он управляет только чтением. Если ты один раз сделал obj.method = 123, ты просто положил значение в obj.__dict__ и тем самым «перекрыл» метод для этого конкретного объекта.
Дескрипторы в Python — это механизм, который позволяет вынести логику доступа к атрибутам в отдельные объекты. Любой объект с __get__, __set__, __delete__, положенный как атрибут класса, становится дескриптором и управляет тем, что происходит при чтении, записи и удалении этого атрибута. На дескрипторах реализованы property, методы, classmethod и staticmethod. На практике их применяют для переиспользуемых «умных» полей: валидация, логирование, lazy‑свойства и т.д.