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

Что такое метод __set__() дескриптора в Python?

2.2 Middle🔥 61 комментариев
#Python Core#Soft Skills

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

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

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

Метод set() дескриптора в Python

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

Основы дескрипторов

Дескриптор — это объект, который реализует как минимум один из следующих методов:

  • __get__() — вызывается при чтении атрибута
  • __set__() — вызывается при присваивании значения атрибуту
  • __delete__() — вызывается при удалении атрибута

Дескриптор с методом __set__() называется data descriptor (дескриптор данных), так как он контролирует присваивание значений.

Синтаксис метода set()

class Descriptor:
    def __get__(self, obj, objtype=None):
        """Вызывается при чтении атрибута"""
        return obj.__dict__.get(value, None)
    
    def __set__(self, obj, value):
        """Вызывается при присваивании значения"""
        # obj — экземпляр класса
        # value — присваиваемое значение
        obj.__dict__[value] = value
    
    def __delete__(self, obj):
        """Вызывается при удалении атрибута"""
        del obj.__dict__[value]

class MyClass:
    attr = Descriptor()

# Использование
obj = MyClass()
obj.attr = 10  # Вызовет __set__()
print(obj.attr)  # Вызовет __get__()
del obj.attr  # Вызовет __delete__()

Практические примеры

Пример 1: Валидация значений

Частое применение дескрипторов — валидация при присваивании:

class ValidatedInteger:
    def __init__(self, name):
        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, int):
            raise TypeError(f"{self.name} должен быть int, получен {type(value).__name__}")
        if value < 0:
            raise ValueError(f"{self.name} не может быть отрицательным")
        obj.__dict__[self.name] = value

class Person:
    age = ValidatedInteger(age)
    
    def __init__(self, name, age):
        self.name = name
        self.age = age  # Вызывает __set__()

p = Person("Alice", 25)
print(p.age)  # 25

p.age = "invalid"  # TypeError: age должен быть int
p.age = -5  # ValueError: age не может быть отрицательным

Пример 2: Ленивое вычисление (lazy evaluation)

class LazyProperty:
    def __init__(self, func):
        self.func = func
        self.name = func.__name__
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        # Вычисляем значение только при первом обращении
        value = self.func(obj)
        # Сохраняем результат в __dict__ для последующих обращений
        obj.__dict__[self.name] = value
        return value

class DataProcessor:
    def __init__(self, data):
        self.data = data
    
    @LazyProperty
    def processed_data(self):
        print("Обработка данных...")
        return [x * 2 for x in self.data]

proc = DataProcessor([1, 2, 3])
print(proc.processed_data)  # "Обработка данных..." [2, 4, 6]
print(proc.processed_data)  # Уже в кэше [2, 4, 6]

Пример 3: Логирование изменений

class LoggedProperty:
    def __init__(self, name):
        self.name = name
        self.private_name = f_logged_{name}
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name, None)
    
    def __set__(self, obj, value):
        old_value = getattr(obj, self.private_name, None)
        print(f"Изменение {self.name}: {old_value} -> {value}")
        setattr(obj, self.private_name, value)

class Config:
    debug_mode = LoggedProperty(debug_mode)
    
    def __init__(self):
        self.debug_mode = False

config = Config()
config.debug_mode = True  # Изменение debug_mode: False -> True
config.debug_mode = False  # Изменение debug_mode: True -> False

Встроенные примеры дескрипторов в Python

Пython активно использует дескрипторы в своей стандартной библиотеке:

  • @property — самый частый дескриптор для создания управляемых свойств
  • @staticmethod и @classmethod — дескрипторы методов
  • @classmethod — создает методы класса через дескриптор
class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Радиус должен быть положительным")
        self._radius = value
    
    @property
    def area(self):
        return 3.14159 * self._radius ** 2

# @property реализует __get__() и __set__() под капотом

Порядок приоритета в Python

Важно понимать, что дескрипторы данных имеют приоритет над словарем экземпляра:

class Priority:
    def __set__(self, obj, value):
        print("Дескриптор __set__()")
        obj.__dict__[attr] = value

p = Priority()
p.attr = 1  # Вызывает __set__(), несмотря на наличие в __dict__

Важные замечания

  • Дескрипторы работают только при определении на уровне класса, не экземпляра
  • __set__() должен либо установить значение, либо вызвать исключение
  • Для оптимизации производительности используйте слоты вместо __dict__
  • Дескрипторы — это основа работы properties, методов и других механизмов Python

Метод __set__() — это мощный инструмент для создания чистого, безопасного и контролируемого кода при работе с атрибутами объектов.

Что такое метод __set__() дескриптора в Python? | PrepBro