← Назад к вопросам
Что такое протокол дескрипторов?
2.0 Middle🔥 181 комментариев
#DevOps и инфраструктура#Django
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Протокол дескрипторов (Descriptor Protocol)
Дескриптор — это объект Python, который реализует протокол дескрипторов и контролирует доступ к атрибутам другого объекта. Дескрипторы позволяют вам перехватывать операции получения, установки и удаления атрибутов, что дает большую власть над поведением объектов.
Магические методы дескриптора
Протокол дескрипторов определяется тремя магическими методами:
class Descriptor:
def __get__(self, obj, objtype=None):
"""Вызывается при получении атрибута (attr = instance.attr)"""
return "получение"
def __set__(self, obj, value):
"""Вызывается при установке атрибута (instance.attr = value)"""
print(f"установка значения {value}")
def __delete__(self, obj):
"""Вызывается при удалении атрибута (del instance.attr)"""
print("удаление атрибута")
Data descriptor содержит __get__ и __set__ (или __delete__)
Non-data descriptor содержит только __get__
Простой пример
class Temperature:
"""Дескриптор для хранения температуры в градусах Цельсия"""
def __init__(self, name: str):
self.name = name
self.value = 0
def __get__(self, obj, objtype=None):
"""Возвращаем температуру в Цельсии"""
if obj is None:
return self
print(f"Получаем значение {self.name}")
return self.value
def __set__(self, obj, value: float):
"""Проверяем значение перед установкой"""
if value < -273.15:
raise ValueError("Температура не может быть ниже абсолютного нуля")
print(f"Устанавливаем {self.name} = {value}°C")
self.value = value
def __delete__(self, obj):
print(f"Удаляем {self.name}")
self.value = None
class Sensor:
"""Класс, использующий дескриптор"""
temperature = Temperature("temperature")
def __init__(self):
self.temperature = 20.0
# Использование
sensor = Sensor()
print(sensor.temperature) # "Получаем значение temperature" → 20.0
sensor.temperature = 25.0 # "Устанавливаем temperature = 25.0°C"
del sensor.temperature # "Удаляем temperature"
Практический пример: валидированное свойство
class ValidatedString:
"""Дескриптор для строк с минимальной длиной"""
def __init__(self, min_length: int = 1, max_length: int = 255):
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(f"_{self.name}", "")
def __set__(self, obj, value: str):
if not isinstance(value, str):
raise TypeError(f"{self.name} должно быть строкой")
if len(value) < self.min_length:
raise ValueError(
f"{self.name} должно быть не менее {self.min_length} символов"
)
if len(value) > self.max_length:
raise ValueError(
f"{self.name} должно быть не более {self.max_length} символов"
)
obj.__dict__[f"_{self.name}"] = value
class User:
username = ValidatedString(min_length=3, max_length=20)
email = ValidatedString(min_length=5, max_length=100)
def __init__(self, username: str, email: str):
self.username = username
self.email = email
# Использование
try:
user = User("ab", "test@example.com") # Ошибка: username слишком короткий
except ValueError as e:
print(f"Ошибка: {e}")
user = User("alice", "alice@example.com")
print(user.username) # "alice"
Пример: вычисляемое свойство (computed property)
class Radius:
"""Дескриптор для радиуса круга"""
def __init__(self):
self.value = 0
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.value
def __set__(self, obj, value):
if value <= 0:
raise ValueError("Радиус должен быть положительным")
self.value = value
class Circle:
radius = Radius()
@property
def area(self):
"""Площадь круга (вычисляется динамически)"""
import math
return math.pi * self.radius ** 2
@property
def circumference(self):
"""Периметр круга"""
import math
return 2 * math.pi * self.radius
circle = Circle()
circle.radius = 5
print(f"Площадь: {circle.area:.2f}") # 78.54
print(f"Периметр: {circle.circumference:.2f}") # 31.42
Дескрипторы в Python: свойства (properties)
class Temperature:
def __init__(self, celsius: float = 0):
self._celsius = celsius
@property
def celsius(self) -> float:
"""Получить температуру в Цельсии"""
return self._celsius
@celsius.setter
def celsius(self, value: float):
"""Установить температуру в Цельсии"""
if value < -273.15:
raise ValueError("Температура ниже абсолютного нуля")
self._celsius = value
@property
def fahrenheit(self) -> float:
"""Получить температуру в Фаренгейте"""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value: float):
"""Установить температуру в Фаренгейте"""
self._celsius = (value - 32) * 5/9
# Использование
temp = Temperature()
temp.celsius = 0
print(f"Celsius: {temp.celsius}, Fahrenheit: {temp.fahrenheit}") # 0, 32.0
temp.fahrenheit = 68 # Устанавливаем через Фаренгейты
print(f"Celsius: {temp.celsius}") # 20.0
Дескрипторы для методов
class BoundMethod:
"""Дескриптор для связывания методов"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
return self.func
# Возвращаем функцию, привязанную к объекту
def bound_method(*args, **kwargs):
return self.func(obj, *args, **kwargs)
return bound_method
class MyClass:
def __init__(self, value: int):
self.value = value
def method(self):
return f"Value: {self.value}"
obj = MyClass(42)
print(obj.method()) # "Value: 42"
Порядок разрешения атрибутов (MRO для дескрипторов)
- Data descriptor (с
__get__и__set__или__delete__) — высший приоритет - Instance dict (
obj.__dict__["attr"]) - Non-data descriptor (только с
__get__) - Class dict (
type(obj).__dict__["attr"]) - MRO (иерархия наследования)
class DataDescriptor:
def __get__(self, obj, objtype=None):
return "data descriptor"
def __set__(self, obj, value):
pass
class NonDataDescriptor:
def __get__(self, obj, objtype=None):
return "non-data descriptor"
class MyClass:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()
obj = MyClass()
obj.__dict__["data_desc"] = "instance value"
obj.__dict__["non_data_desc"] = "instance value"
print(obj.data_desc) # "data descriptor" (data descriptor имеет приоритет)
print(obj.non_data_desc) # "instance value" (instance dict имеет приоритет)
Практическое применение
- Валидация данных — проверка типов и значений
- Lazy loading — отложенная загрузка данных
- Кэширование — сохранение вычисленных значений
- Вычисляемые свойства — динамическое вычисление значений
- ORM (SQLAlchemy, Django ORM) — управление атрибутами моделей
- Декораторы — функции работают через дескрипторы
Дескрипторы — это мощный инструмент для контроля доступа к атрибутам и создания сложной логики в Python-классах.