Какую роль выполняет метод __set__ в протоколе дескрипторов Python

Ответ

Метод __set__(self, instance, value) является частью протокола data descriptor (дескриптора данных) в Python. Он перехватывает операцию присваивания значения атрибуту, позволяя выполнить валидацию, преобразование типов или другую логику перед сохранением.

Он вызывается автоматически, когда происходит попытка присвоить значение атрибуту, управляемому дескриптором.

Параметры:

  • self: экземпляр самого дескриптора.
  • instance: экземпляр класса, в котором находится атрибут (например, MyClass()).
  • value: присваиваемое значение.

Пример: Дескриптор для неотрицательных чисел

Чтобы дескриптор знал имя атрибута, с которым он работает, используется метод __set_name__ (Python 3.6+).

class NonNegative:
    def __set_name__(self, owner, name):
        # Сохраняем имя атрибута, например, 'price' или 'quantity'
        self.private_name = '_' + name

    def __get__(self, instance, owner):
        # Возвращаем значение из приватного атрибута экземпляра
        return getattr(instance, self.private_name)

    def __set__(self, instance, value):
        # Логика валидации перед присваиванием
        if value < 0:
            raise ValueError(f"Значение не может быть отрицательным.")
        # Сохраняем значение в приватный атрибут экземпляра
        setattr(instance, self.private_name, value)

class Product:
    price = NonNegative()
    quantity = NonNegative()

    def __init__(self, price, quantity):
        self.price = price
        self.quantity = quantity

# Использование
item = Product(100, 5)
print(f"Цена: {item.price}, Количество: {item.quantity}") # Цена: 100, Количество: 5

item.price = 200 # OK

# Эта строка вызовет ValueError
try:
    item.quantity = -1
except ValueError as e:
    print(e)

Ключевые моменты:

  1. Data Descriptor: Наличие метода __set__ (или __delete__) делает дескриптор дескриптором данных, который имеет более высокий приоритет, чем атрибуты в словаре экземпляра (__dict__).
  2. Валидация: Это основной сценарий использования __set__ — проверка данных на соответствие определенным правилам.
  3. Инкапсуляция: Позволяет скрыть внутреннюю логику хранения данных за простым интерфейсом атрибута.