__get__, __set__ и __delete__.  При назначении дескриптора атрибуту класса, эти методы будут автоматически вызываться при доступе, изменении или удалении атрибута соответственно.__setattr__(self, name, value) в классе.  Этот метод вызывается при каждой попытке присвоить значение атрибуту объекта.  Внутри метода можно выполнить нужные действия перед присвоением значения. Важно вызывать super().__setattr__(name, value), чтобы избежать бесконечной рекурсии.__setattr__:
  
class MyClass:
    def __setattr__(self, name, value):
        print(f"Атрибут {name} изменен на {value}")
        super().__setattr__(name, value)
obj = MyClass()
obj.x = 10  # Выведет: Атрибут x изменен на 10
Для создания методов, которые вызываются автоматически при изменении атрибутов объекта в Python, можно использовать несколько подходов:
__get__, __set__ и __delete__.
    class ValidatedString:
    def __init__(self, storage_name):
        self.storage_name = storage_name
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.storage_name]
    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise ValueError("Значение должно быть строкой")
        instance.__dict__[self.storage_name] = value
class MyClass:
    my_string = ValidatedString('__my_string')
    def __init__(self, my_string):
        self.my_string = my_string # Используем дескриптор для установки значения
# Пример использования
obj = MyClass("Hello")
print(obj.my_string) # Output: Hello
try:
    obj.my_string = 123
except ValueError as e:
    print(e) # Output: Значение должно быть строкой
    В этом примере ValidatedString - дескриптор. При присвоении значения атрибуту my_string объекта MyClass, вызывается метод __set__ дескриптора, позволяющий выполнять валидацию или другие действия.
__setattr__: Метод __setattr__ перехватывает все попытки присвоения значения атрибуту объекта. Это дает полный контроль над процессом присвоения, но требует аккуратности, чтобы избежать бесконечной рекурсии.
    class MyClass:
    def __init__(self, x, y):
        self._x = x  # Используем другое имя для хранения значения
        self._y = y
    def __setattr__(self, name, value):
        if name == 'x':
            print(f"Изменяем атрибут x на {value}")
            self.__dict__['_x'] = value # Обращаемся к __dict__ напрямую, чтобы избежать рекурсии
        elif name == 'y':
            print(f"Изменяем атрибут y на {value}")
            self.__dict__['_y'] = value
        else:
            super().__setattr__(name, value) # Для остальных атрибутов используем стандартное поведение
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
      self._x = value
    @property
    def y(self):
        return self._y
    @y.setter
    def y(self, value):
      self._y = value
# Пример использования
obj = MyClass(10, 20)
obj.x = 100 # Output: Изменяем атрибут x на 100
obj.y = 200 # Output: Изменяем атрибут y на 200
    Важно использовать self.__dict__[name] = value внутри __setattr__, чтобы избежать рекурсивного вызова __setattr__.
@property_name.setter) вызываются при изменении значения атрибута.
        class MyClass:
    def __init__(self, x):
        self._x = x
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        print(f"Атрибут x изменен на {value}")
        self._x = value
# Пример использования
obj = MyClass(10)
obj.x = 100 # Output: Атрибут x изменен на 100
        Свойства удобны для простого перехвата операций чтения и записи атрибутов, но они не перехватывают присвоения напрямую в __dict__.
Выбор между этими подходами зависит от конкретных потребностей и сложности задачи. Дескрипторы предоставляют наиболее гибкий и мощный механизм, но требуют больше понимания. __setattr__ дает полный контроль, но может быть сложен в реализации. Свойства - наиболее простой и интуитивно понятный подход для простых случаев.