Что такое дескрипторы в Python и как они используются в Django

«Что такое дескрипторы в Python и как они используются в Django» — вопрос из категории Django, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Django активно использует этот механизм для реализации полей моделей (models.Field). Каждое поле в модели Django (например, CharField, IntegerField) является дескриптором.

Почему это важно для Django? Дескрипторы позволяют полям модели:

  • Преобразовывать типы данных: значение из базы данных (например, строка) автоматически преобразуется в нужный тип Python (например, datetime.date) при чтении (__get__).
  • Выполнять валидацию: при присваивании значения полю (__set__) можно проверить его на соответствие правилам (например, max_length).
  • Реализовывать ленивую загрузку: связанные объекты (ForeignKey) не загружаются из базы данных до тех пор, пока к ним не обратятся (__get__).

Простой пример дескриптора (чистый Python):

Этот дескриптор проверяет, что присваиваемое значение является положительным числом.

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

    def __get__(self, obj, objtype=None):
        # Получаем значение из приватного атрибута экземпляра
        return getattr(obj, self.private_name)

    def __set__(self, obj, value):
        # Проверяем значение перед присваиванием
        if not isinstance(value, (int, float)) or value <= 0:
            raise ValueError(f"'{self.public_name}' must be a positive number.")
        setattr(obj, self.private_name, value)

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

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

# Использование
product = Product(100.5, 10)
print(product.price)  # Вывод: 100.5

try:
    product.price = -50 # Вызовет ValueError
except ValueError as e:
    print(e) # Вывод: 'price' must be a positive number.

Как это выглядит в Django:

Когда вы объявляете поле title = models.CharField(max_length=100), вы создаете экземпляр дескриптора CharField. Django при инициализации модели связывает этот дескриптор с атрибутом title. Присваивание my_article.title = "..." вызывает метод __set__ дескриптора, который выполняет проверку длины.