Ответ
Протокол дескрипторов в Python — это механизм, позволяющий объекту (дескриптору) управлять доступом к атрибутам другого объекта (владельца). Дескриптор должен реализовать один или несколько из следующих методов:
__get__(self, instance, owner): для получения значения атрибута.__set__(self, instance, value): для установки значения атрибута.__delete__(self, instance): для удаления атрибута.
Дескрипторы работают только тогда, когда они являются атрибутами класса, а не экземпляра.
Пример: дескриптор для валидации положительных чисел
class PositiveNumber:
def __set_name__(self, owner, name):
# Сохраняем имя атрибута, к которому привязан дескриптор
self.private_name = '_' + name
def __get__(self, instance, owner):
return getattr(instance, self.private_name)
def __set__(self, instance, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError(f"Значение должно быть положительным числом, а не {value}")
setattr(instance, self.private_name, value)
class Product:
price = PositiveNumber()
quantity = PositiveNumber()
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
# Использование
book = Product(150, 10)
print(book.price) # 150
try:
book.price = -50 # Вызовет ValueError
except ValueError as e:
print(e)
Типы дескрипторов:
- Data Descriptor: реализует
__set__или__delete__. Имеет приоритет над словарем экземпляра (__dict__). - Non-Data Descriptor: реализует только
__get__. Имеет меньший приоритет.
Этот протокол является основой для многих возможностей Python, включая @property, @staticmethod, @classmethod и работу слотов (__slots__).
Ответ 18+ 🔞
А, ну это же та самая магия, из- которой новички мозги на стенку размазывают! Протокол дескрипторов в Python — это, по сути, такой легальный способ подсунуть свой код в момент, когда кто-то пытается достучаться до атрибута объекта. Как будто ты ставишь своего человека на проходной, и он всех проверяет.
Чувак, это работает так: есть объект-дескриптор. Он не просто какая-то переменная, а хитрая жопа с методами-ловушками. Если его объявить как атрибут класса, то когда ты у какого-нибудь экземпляра этого класса попытаешься взять или записать значение в этот атрибут — сработает не простое присваивание, а твои методы.
Какие методы? Да вот эти, блядь:
__get__(self, instance, owner): Вызывается, когда кто-то пытается прочитать значение.instance— это тот самый экземпляр, у которого спрашивают, аowner— его родной класс.__set__(self, instance, value): Вызывается, когда кто-то пытается записать значение.value— это то, что ему суют в руки.__delete__(self, instance): Вызывается, когда кто-то пытается удалить атрибут командойdel.
И главное, ёпта, правило номер один: эта магия работает только если дескриптор — атрибут класса. Если ты его в __init__ экземпляру прилепишь — будет обычное поле, и все твои __get__ и __set__ просто проигнорируют, в рот меня чих-пых!
Вот тебе живой пример: дескриптор, который орёт, если ему подсовывают не положительное число.
class PositiveNumber:
def __set_name__(self, owner, name):
# Тут мы хитрим: сохраняем имя, но с подчёркиванием, чтобы спрятать реальное значение
self.private_name = '_' + name
def __get__(self, instance, owner):
# Когда просят значение — отдаём то, что спрятали
return getattr(instance, self.private_name)
def __set__(self, instance, value):
# А вот тут проверяем! Не число или меньше нуля? Нахуй такое!
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError(f"Значение должно быть положительным числом, а не {value}")
# Если всё ок — прячем значение под заготовленное имя
setattr(instance, self.private_name, value)
class Product:
# Вот оно! Дескрипторы объявлены на уровне класса!
price = PositiveNumber()
quantity = PositiveNumber()
def __init__(self, price, quantity):
self.price = price # Это вызовет __set__ у дескриптора price!
self.quantity = quantity
# Пробуем
book = Product(150, 10)
print(book.price) # 150 — сработал __get__
try:
book.price = -50 # А это вызовет __set__, и он нас пошлёт
except ValueError as e:
print(e) # "Значение должно быть положительным числом, а не -50"
Ещё есть разделение на два типа, от которого приоритет зависит:
- Data Descriptor: У него есть
__set__или__delete__. Эта тварь главнее словаря экземпляра (__dict__). Сначала Python спросит у неё. - Non-Data Descriptor: У него только
__get__. Он скромнее, и если в самом экземпляре уже есть такое поле — возьмут его, а не побегут к дескриптору.
Этот протокол, блядь, — фундамент для кучи крутых штук в Python. Все эти @property, @staticmethod, @classmethod и даже __slots__ — они все внутри используют дескрипторы, просто прикидываются синтаксическим сахаром!