Что такое дескриптор в 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 и других фреймворках для управления атрибутами моделей.