← Назад к вопросам

Что такое дескрипторы в Python?

2.0 Middle🔥 181 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое дескрипторы в 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 имеют приоритет