Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Дескрипторы в Django
Дескриптор — это объект Python, определяющий поведение атрибутов класса. Это механизм для перехвата доступа к атрибутам (чтение, запись, удаление). Django интенсивно использует дескрипторы для реализации ORM функциональности.
Протокол дескриптора
Дескриптор — это класс, реализующий один или несколько методов:
class Descriptor:
def __get__(self, obj, objtype=None):
"""Вызывается при чтении атрибута"""
print(f"Получение значения из {obj}")
return obj.__dict__.get('_value', None)
def __set__(self, obj, value):
"""Вызывается при присваивании значения"""
print(f"Установка значения: {value}")
obj.__dict__['_value'] = value
def __delete__(self, obj):
"""Вызывается при удалении атрибута"""
print(f"Удаление значения")
del obj.__dict__['_value']
class MyClass:
attr = Descriptor()
obj = MyClass()
obj.attr = 42 # Вызовет __set__
print(obj.attr) # Вызовет __get__
del obj.attr # Вызовет __delete__
Дескрипторы в Django ORM
Django использует дескрипторы для полей модели:
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
age = models.IntegerField()
class Meta:
db_table = 'users'
user = User(name="Alice", email="alice@example.com", age=30)
print(user.name) # __get__ дескриптора
user.age = 31 # __set__ дескриптора
Кажд Django Field — это дескриптор, который:
- При чтении возвращает значение из dict экземпляра
- При установке валидирует и сохраняет значение
- Переводит типы (строка базы данных в Python объект)
Создание собственного дескриптора в Django
class ValidatedString:
"""Дескриптор для валидированной строки"""
def __init__(self, min_length=0, max_length=None):
self.min_length = min_length
self.max_length = max_length
self.name = None
def __set_name__(self, owner, name):
"""Вызывается при создании класса"""
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name, '')
def __set__(self, obj, value):
if not isinstance(value, str):
raise TypeError(f"{self.name} должна быть строкой")
if len(value) < self.min_length:
raise ValueError(f"Минимум {self.min_length} символов")
if self.max_length and len(value) > self.max_length:
raise ValueError(f"Максимум {self.max_length} символов")
obj.__dict__[self.name] = value
class Product(models.Model):
# Django поле
name = models.CharField(max_length=100)
# Дескриптор для валидации
_sku = ValidatedString(min_length=3, max_length=10)
@property
def sku(self):
return self._sku
@sku.setter
def sku(self, value):
self._sku = value
# Использование
product = Product(name="Laptop")
product.sku = "LAPTOP-001" # OK
product.sku = "AB" # ValueError: Минимум 3 символов
Свойства (property) — встроенный дескриптор
property — это встроенный дескриптор Python:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""__get__ дескриптора"""
return self._radius
@radius.setter
def radius(self, value):
"""__set__ дескриптора"""
if value <= 0:
raise ValueError("Радиус должен быть положительным")
self._radius = value
@radius.deleter
def radius(self):
"""__delete__ дескриптора"""
del self._radius
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.radius) # 5
print(circle.area) # 78.53975
circle.radius = 10 # Валидация срабатывает
Django Related Fields — дескрипторы для связей
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# ForeignKey это дескриптор!
book = Book.objects.get(id=1)
print(book.author) # __get__ возвращает Author объект
book.author = new_author # __set__ сохраняет связь
# Реверсивная связь
author = Author.objects.get(id=1)
author.book_set.all() # ReverseOneToOneDescriptor
Кэширование через дескриптор
class CachedProperty:
"""Дескриптор для ленивого кэширования"""
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, obj, objtype=None):
if obj is None:
return self
# Кэш в объекте
if f'_cache_{self.name}' not in obj.__dict__:
obj.__dict__[f'_cache_{self.name}'] = self.func(obj)
return obj.__dict__[f'_cache_{self.name}']
class User(models.Model):
name = models.CharField(max_length=100)
@CachedProperty
def expensive_property(self):
"""Вычисляется только один раз"""
print("Дорогое вычисление...")
return sum_of_user_posts(self)
user = User.objects.get(id=1)
print(user.expensive_property) # Вычисляет
print(user.expensive_property) # Возвращает кэш
Порядок разрешения атрибутов (MRO)
При доступе к атрибуту Python проверяет в таком порядке:
# 1. Data дескрипторы класса (с __get__ и __set__)
# 2. Атрибуты экземпляра (__dict__)
# 3. Non-data дескрипторы класса (только __get__)
# 4. Атрибуты класса
# 5. __getattr__ (если определён)
class Descriptor:
def __get__(self, obj, objtype=None):
return "descriptor"
class MyClass:
attr = Descriptor() # Дескриптор класса
obj = MyClass()
obj.__dict__['attr'] = 'instance' # Атрибут экземпляра
print(obj.attr) # Выведет "descriptor" (дескриптор имеет приоритет)
Лучшие практики
- Используй property для простой валидации и трансформации
- Используй дескрипторы для сложной логики, переиспользуемой в разных классах
- Помни о производительности — дескрипторы добавляют overhead при каждом доступе
- Тестируй граничные случаи — ошибки в set могут привести к несогласованности данных
- Документируй поведение — дескрипторы не очевидны с первого взгляда
Дескрипторы — это фундамент Django ORM. Понимание их работы критично для эффективной работы с моделями и оптимизации кода.