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

Что является дескриптором в Python?

1.8 Middle🔥 91 комментариев
#Git и VCS#Тестирование

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

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

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

Дескрипторы в Python

Дескриптор (descriptor) — это объект, определяющий поведение атрибутов при доступе, присваивании или удалении. Дескрипторы — это мощный механизм в Python, который позволяет контролировать взаимодействие с атрибутами класса и реализовать такие паттерны как свойства (properties), методы, статические методы и другие.

Протокол дескриптора

Дескриптор — это класс, который реализует один или несколько методов из дескриптор-протокола:

Методы дескриптора:

  • __get__(self, obj, objtype=None) — вызывается при доступе к атрибуту
  • __set__(self, obj, value) — вызывается при присваивании значения атрибуту
  • __delete__(self, obj) — вызывается при удалении атрибута

Если дескриптор реализует __set__ или __delete__, он называется data descriptor (дескриптор данных). Если реализует только __get__, — это non-data descriptor (дескриптор без данных).

Простой пример дескриптора

class Descriptor:
    def __init__(self, name):
        self.name = name
        self.value = None
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        print(f"Getting {self.name}")
        return self.value
    
    def __set__(self, obj, value):
        print(f"Setting {self.name} to {value}")
        self.value = value
    
    def __delete__(self, obj):
        print(f"Deleting {self.name}")
        self.value = None

class MyClass:
    attr = Descriptor("attr")

obj = MyClass()
obj.attr = 42        # Setting attr to 42
print(obj.attr)      # Getting attr\n42
del obj.attr         # Deleting attr

Property — встроенный дескриптор

@property — это встроенный дескриптор для создания вычисляемых свойств:

class User:
    def __init__(self, first_name, last_name):
        self._first_name = first_name
        self._last_name = last_name
    
    @property
    def full_name(self):
        """Вычисляемое свойство — read-only"""
        return f"{self._first_name} {self._last_name}"
    
    @full_name.setter
    def full_name(self, name):
        """Позволяет установить full_name"""
        first, last = name.split()
        self._first_name = first
        self._last_name = last
    
    @full_name.deleter
    def full_name(self):
        """Позволяет удалить full_name"""
        self._first_name = None
        self._last_name = None

user = User("John", "Doe")
print(user.full_name)           # John Doe
user.full_name = "Jane Smith"
print(user.full_name)           # Jane Smith
del user.full_name
print(user._first_name)         # None

Валидирующий дескриптор

class ValidatedString:
    def __init__(self, min_length=0, max_length=None):
        self.min_length = min_length
        self.max_length = max_length
        self.data = {}
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return self.data.get(id(obj), None)
    
    def __set__(self, obj, value):
        if not isinstance(value, str):
            raise TypeError("Value must be a string")
        if len(value) < self.min_length:
            raise ValueError(f"String too short (min: {self.min_length})")
        if self.max_length and len(value) > self.max_length:
            raise ValueError(f"String too long (max: {self.max_length})")
        self.data[id(obj)] = value

class Product:
    name = ValidatedString(min_length=1, max_length=100)
    description = ValidatedString(max_length=500)

product = Product()
product.name = "Laptop"           # OK
product.name = ""                 # ValueError: String too short
product.name = 123                # TypeError: Value must be a string

Классметоды и статические методы — дескрипторы

@classmethod и @staticmethod — это встроенные дескрипторы:

class MyClass:
    class_var = "shared"
    
    @staticmethod
    def static_method():
        """Не получает ни self, ни cls"""
        return "static"
    
    @classmethod
    def class_method(cls):
        """Получает класс вместо экземпляра"""
        return f"Called on {cls.__name__}"
    
    def instance_method(self):
        """Обычный метод"""
        return "instance"

print(MyClass.static_method())      # static
print(MyClass.class_method())       # Called on MyClass

obj = MyClass()
print(obj.instance_method())        # instance

Дескриптор с использованием слабых ссылок

Для хранения значений отдельно для каждого объекта лучше использовать слабые ссылки:

import weakref

class BoundMethod:
    def __init__(self, func):
        self.func = func
        self.data = weakref.WeakKeyDictionary()
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return lambda *args, **kwargs: self.func(obj, *args, **kwargs)

Порядок разрешения атрибутов (MRO)

Когда вы обращаетесь к атрибуту, Python следует этому порядку:

  1. Data descriptors из класса и его предков
  2. Атрибуты экземпляра (obj.__dict__)
  3. Non-data descriptors из класса
  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 MyClass:
    data_desc = DataDescriptor()
    non_data_desc = NonDataDescriptor()

obj = MyClass()
obj.__dict__['data_desc'] = "instance attr"
obj.__dict__['non_data_desc'] = "instance attr"

print(obj.data_desc)        # data descriptor (приоритет data descriptor)
print(obj.non_data_desc)    # instance attr (приоритет экземпляра)

Практическое применение

ORM (SQLAlchemy) использует дескрипторы:

from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)  # Column — дескриптор
    name = Column(String(100))

Заключение

Дескрипторы — это фундаментальный механизм Python, который стоит за свойствами, методами и множеством фреймворков. Понимание дескрипторов позволяет писать более мощный и гибкий код, создавать элегантные API и глубже понимать работу Python.

Что является дескриптором в Python? | PrepBro