Что такое дескриптор в Python?

Ответ

Дескриптор в Python — это объект, который реализует один или несколько методов протокола дескрипторов: __get__, __set__ или __delete__. Он позволяет контролировать доступ к атрибутам другого класса (чтение, запись, удаление).

Почему используются дескрипторы? Они предоставляют мощный механизм для:

  • Валидации данных: Проверка значений перед их присвоением атрибуту.
  • Ленивой инициализации: Вычисление значения атрибута только при первом доступе.
  • Управления доступом: Реализация логики при получении или установке атрибута.
  • Повторного использования логики: Инкапсуляция логики доступа к атрибутам в отдельном, переиспользуемом объекте.

Методы протокола дескрипторов:

  • __get__(self, instance, owner): Вызывается при чтении атрибута.
    • instance: Экземпляр класса, к которому принадлежит атрибут (или None, если доступ через класс).
    • 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):
        if instance is None:
            return self # Доступ через класс, возвращаем сам дескриптор
        return getattr(instance, self.private_name)

    def __set__(self, instance, value):
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError("Значение должно быть положительным числом.")
        setattr(instance, self.private_name, value)

class Product:
    price = PositiveNumber() # Дескрипторный атрибут

    def __init__(self, name, price):
        self.name = name
        self.price = price # Вызовет __set__ дескриптора

# Использование
p1 = Product("Laptop", 1200)
print(f"Product: {p1.name}, Price: {p1.price}") # Вызовет __get__

try:
    p2 = Product("Monitor", -50) # Вызовет ValueError
except ValueError as e:
    print(f"Ошибка: {e}")

p1.price = 1500.50 # Вызовет __set__
print(f"New price: {p1.price}")

# p1.price = "invalid" # Вызовет ValueError

Типы дескрипторов:

  • Data descriptor (дескриптор данных): Реализует __set__ или __delete__ (и обычно __get__). Имеет приоритет над атрибутами в словаре экземпляра (instance.__dict__). Пример: property.
  • Non-data descriptor (не-дескриптор данных): Реализует только __get__. Имеет более низкий приоритет, чем атрибуты в instance.__dict__. Пример: методы класса (@classmethod, @staticmethod).

Дескрипторы являются фундаментальным механизмом в Python и лежат в основе таких встроенных функций, как @property, @classmethod, @staticmethod, а также используются в ORM и других фреймворках для управления атрибутами моделей.

Ответ 18+ 🔞

А, дескрипторы в Python, да? Ну это ж классика, блядь! Сидит такой один объект, а другой на нём как на кресле-качалке катается, и всё по протоколу, сука!

Смотри, вот представь: есть у тебя обычный атрибут в классе. Ты ему — раз, присвоил значение, он и сидит, как пень, нихуя не делает. А дескриптор — это как хитрая жопа с ушами. Ты к нему обращаешься, а он тебе: «Ага, щас, погоди, я тут свою логику проверну».

Зачем это, спросишь? Да чтобы не быть распиздяем, вот зачем!

  • Валидация, блядь. Чтоб не присвоили цене товара минус пятьсот рублей, а потом охуевали, куда деньги ушли.
  • Ленивые вычисления. Чтоб не грузить всё сразу, а считать только когда реально спросили. Экономия, мать её, ресурсов!
  • Контроль доступа. Чтоб не любой левый мог нахулиганить с твоими данными.

А как он работает, этот протокол? Да через три волшебных метода, хули!

  • __get__(self, instance, owner): Вызывается, когда кто-то читает атрибут. instance — это объект, к которому лезут, а owner — сам класс. Если лезут через класс (а не через объект), то instance будет None, имей в виду!
  • __set__(self, instance, value): Вызывается, когда пишут в атрибут. Вот тут-то и можно вставить свою проверку и орать ValueError, если чушь какую несут.
  • __delete__(self, instance): Вызывается, когда атрибут удаляют (del obj.attr).

Вот тебе живой пример, чтобы не быть мудаком и не принимать отрицательные цены:

class PositiveNumber:
    def __set_name__(self, owner, name):
        # Сохраняем имя атрибута, чтобы хранить значение в словаре экземпляра
        self.private_name = '_' + name

    def __get__(self, instance, owner):
        if instance is None:
            return self # Доступ через класс, возвращаем сам дескриптор
        return getattr(instance, self.private_name)

    def __set__(self, instance, value):
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError("Значение должно быть положительным числом.")
        setattr(instance, self.private_name, value)

class Product:
    price = PositiveNumber() # Дескрипторный атрибут

    def __init__(self, name, price):
        self.name = name
        self.price = price # Вызовет __set__ дескриптора

# Использование
p1 = Product("Laptop", 1200)
print(f"Product: {p1.name}, Price: {p1.price}") # Вызовет __get__

try:
    p2 = Product("Monitor", -50) # Вызовет ValueError
except ValueError as e:
    print(f"Ошибка: {e}")

p1.price = 1500.50 # Вызовет __set__
print(f"New price: {p1.price}")

# p1.price = "invalid" # Вызовет ValueError

Видишь? Создаём Product, и цена у него — не просто число, а целый объект-надзиратель PositiveNumber. Как только пытаемся запихнуть минус пятьдесят — ПИЗДЕЦ! ValueError прямо в ебало. Красота, ёпта!

А ещё они бывают двух сортов, как картошка:

  • Data descriptor (дескриптор данных): У него есть __set__ или __delete__. Этот тип — главный пацан, у него приоритет выше, чем у атрибутов в __dict__ объекта. Классический пример — встроенный @property.
  • Non-data descriptor (не-дескриптор данных): У него только __get__. Он скромный, если в самом объекте уже есть атрибут с таким именем, то выиграет объект. Так работают методы с декораторами @classmethod или @staticmethod.

Короче, дескрипторы — это фундамент, на котором построены все эти ваши @property и ORM-фреймворки. Без них Python был бы просто... ну, не Python, блядь.