Что такое дескрипторы в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое дескрипторы в Python?
Дескрипторы — это объекты, которые реализуют методы __get__, __set__ или __delete__ и контролируют доступ к атрибутам класса. Это один из наиболее мощных и в то же время менее понятных механизмов Python, используемый для создания вычисляемых свойств, валидации данных и реализации сложной логики доступа к атрибутам.
Основные методы дескриптора
Дескриптор должен реализовать один или несколько из этих методов:
class Descriptor:
def __get__(self, obj, objtype=None):
# Вызывается при чтении атрибута
print(f"Getting value")
return None
def __set__(self, obj, value):
# Вызывается при установке атрибута
print(f"Setting value to {value}")
def __delete__(self, obj):
# Вызывается при удалении атрибута
print(f"Deleting value")
class MyClass:
attr = Descriptor()
obj = MyClass()
obj.attr = 10 # Вызовет __set__
print(obj.attr) # Вызовет __get__
del obj.attr # Вызовет __delete__
Виды дескрипторов
1. Data Descriptor (дескриптор данных)
Data descriptor реализует __set__ или __delete__. Он имеет приоритет над атрибутами экземпляра:
class ValidatedString:
def __init__(self, name=None):
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name, None)
def __set__(self, obj, value):
if not isinstance(value, str):
raise TypeError(f"{self.name} must be a string")
if len(value) < 3:
raise ValueError(f"{self.name} must be at least 3 characters")
obj.__dict__[self.name] = value
class User:
name = ValidatedString("name")
def __init__(self, name):
self.name = name
user = User("Alice")
print(user.name) # Alice
user.name = "Bob" # OK
user.name = "ab" # ValueError: must be at least 3 characters
2. Non-data Descriptor (дескриптор только для чтения)
Non-data descriptor реализует только __get__. Атрибуты экземпляра имеют приоритет:
class Lazy:
def __init__(self, function):
self.function = function
self.name = function.__name__
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.function(obj)
class Circle:
def __init__(self, radius):
self.radius = radius
@Lazy
def area(self):
print(f"Computing area for radius {self.radius}")
return 3.14159 * self.radius ** 2
circle = Circle(5)
print(circle.area) # Вычисляет и выводит
print(circle.area) # Вычисляет снова (нет кэширования)
Практический пример: property
property — это встроенный дескриптор для создания вычисляемых свойств:
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9
temp = Temperature(0)
print(temp.celsius) # 0
print(temp.fahrenheit) # 32
temp.fahrenheit = 212
print(temp.celsius) # 100
Дескрипторы для валидации
class Validated:
def __init__(self, name, validator):
self.name = name
self.validator = validator
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 self.validator(value):
raise ValueError(f"Invalid value for {self.name}: {value}")
obj.__dict__[self.name] = value
class Person:
age = Validated("age", lambda x: isinstance(x, int) and 0 <= x <= 150)
email = Validated("email", lambda x: "@" in x)
person = Person()
person.age = 25 # OK
person.email = "user@example.com" # OK
person.age = -5 # ValueError
Как Python ищет атрибуты (MRO — Method Resolution Order)
Порядок поиска атрибутов:
# 1. Data descriptors класса (имеют __set__ или __delete__)
# 2. Атрибуты экземпляра
# 3. Non-data descriptors класса (только __get__)
# 4. Атрибуты класса
# 5. __getattr__ (если определён)
Пример для демонстрации:
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 Example:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()
class_attr = "Class Attribute"
obj = Example()
# Data descriptor имеет приоритет
print(obj.data_desc) # Data Descriptor
# Non-data descriptor имеет меньший приоритет
obj.non_data_desc = "Instance Override"
print(obj.non_data_desc) # Instance Override
# Обычный атрибут
obj.class_attr = "Instance Override"
print(obj.class_attr) # Instance Override
Дескриптор для контроля типов
class TypedProperty:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
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, self.expected_type):
raise TypeError(
f"{self.name} must be {self.expected_type.__name__}, "
f"got {type(value).__name__}"
)
obj.__dict__[self.name] = value
class Product:
price = TypedProperty("price", float)
quantity = TypedProperty("quantity", int)
name = TypedProperty("name", str)
product = Product()
product.name = "Laptop"
product.price = 999.99
product.quantity = 5
product.price = "invalid" # TypeError
Применение дескрипторов
- ORM системы (SQLAlchemy, Django ORM) — контроль доступа к полям БД
- Валидация данных — проверка типов и значений
- Lazy loading — отложенная загрузка данных
- Вычисляемые свойства — property, cachedproperty
- Логирование доступа — отслеживание изменений атрибутов
- Привязка данных — связь между атрибутами UI и моделей
Типичные ошибки
❌ Путаница между obj.__dict__ и self.value — внутри дескриптора используй obj.__dict__
❌ Забыли реализовать __get__ — дескриптор не будет работать
❌ Data descriptor не переопределяет attribute — не забывай, что data descriptors имеют приоритет