Есть ли лучший способ обнаружить изменение атрибутов, чем использование декораторов?

Я проводил рефакторинг текстового класса, предназначенного для вывода текста на экран. Недавно я узнал о декораторе @property и подумал, что его использование в этом классе поможет.
Вот урезанная версия кода, который я пишу:

class Text:
    def __init__(self, text, antialias):
        self.text = text
        self.antialias = antialias
        self._render_text()

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self._text = value
        self._requires_render = True

    @property
    def antialias(self):
        return self._antialias

    @antialias.setter
    def antialias(self, value):
        self._antialias = value
        self._requires_render = True

    def _render_text(self):
        self._required_render = False

    def blit(self):
        if self._requires_render:
            self._render_text()
        # blit text to screen

Я заметил, что моей основной мотивацией использования сеттеров и геттеров было изменение атрибута, который заказывал повторный рендеринг текста перед его выводом на экран. Это гарантировало правильное обновление атрибутов текстового объекта в цикле событий. Это также гарантировало, что если несколько свойств, все из которых требуют повторного рендеринга текста, были вызваны последовательно, будет запущен только один повторный рендеринг, поскольку проверка повторного рендеринга происходит только один раз в каждом игровом цикле.
Однако это также казалось чрезмерным, и мне было интересно, есть ли способ написать собственный объект @property, включающий строку self._requires_render. Хотя после некоторых поисков и не найдя ни у кого такой же проблемы, я считаю, что моя логика может быть ошибочной, и существует более простой способ определить, когда обновляются свойства объекта, и запустить некоторый код.

Как я предлагал, прежде чем кто-то проголосовал за публикацию этого вопроса, я предлагаю прочитать docs.python.org/3/library/pathlib.html, чтобы вы могли увидеть, как property реализует протокол дескриптора и как вы можете реализовать свой собственный дескриптор Renderable чтобы вы могли писать text = Renderable() вместо того, чтобы создавать text свойство.

chepner 27.08.2024 19:57

@chepner Я бы посчитал это ответом, чтобы вы могли опубликовать его как ответ на вопрос. Также чтение документации было очень интересным, спасибо за предложение.

r5ne 27.08.2024 20:48

Я не считаю это ответом, потому что это просто написание кода для вас :) Я предоставил ссылку, чтобы вы могли сначала попробовать написать ее самостоятельно и, если необходимо, задать более конкретный вопрос. (Когда этот вопрос находился в стадии постановки, я предложил сделать это вместо того, чтобы одобрять вопрос для публикации, поскольку я чувствовал, что он слишком широк. Другие не согласились и одобрили его, и он был - что неудивительно для меня - почти сразу же отклонен.)

chepner 27.08.2024 21:27

@chepner Как вы думаете, мне следует переписать весь вопрос, зная, что ответ будет более конкретным? Потому что в этом случае что-то очень похожее уже было задано, так что это будет просто косвенный дубликат.

r5ne 28.08.2024 00:38
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
4
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Да, в идеале использовать те же механизмы, что и сам property, но собственность — это не идеал.

И property, и обычные методы в Python используют так называемый протокол descriptor — это означает, что атрибуты, связанные с классом, могут иметь методы __get__ и __set__, которые используются при доступе к значению с нотацией ..

По сути, все, что вам нужно, это метод __set__, который установит атрибут _requires_render и в противном случае обеспечит прозрачный доступ к атрибуту. Вы также можете воспользоваться методом __set_name__, который также связан с протоколом дескриптора и вызывается языком во время создания класса:

class RenderNeeded:
    def __set_name__(self, owner, name):
        self.name = name
    def __get__(self, instance, owner):
        if instance is None:
             return self
        return instance.__dict__[self.name]
    def __set__(self, instance, value):
        instance._requires_render = True
        instance.__dict__[self.name] = value



from functools import wraps


# and for methods instead of variables,
# this is the decorator approach:

def clear_rendering(func):
    @wraps(func)
    def wrapper(self, *args, **kw):
        result = func(self, *args, **kw)
        self._requires_render = False
        return result
    return wrapper
   
class Text:
    def __init__(...):
        ...
    text = RenderNeeded()
    antialias = RenderNeeded()
    
    @clear_rendering
    def blit(self, ...):
        # blit text code ...
        ...

Другой подход, кроме создания индивидуального дескриптора, — настроить метод __setattr__ непосредственно в вашем классе:

class Text:
    def __init__(self, text, antialias):
         ...
    def __setattr__(self, attr, value):
         if attr in {"text", "antialias", ...}:
               # this triggers a recursive call, but there is no problem. Just use `super().__setattr__` to avoid it, if preferred:
               self._requires_render = True
          return super().__setattr__(attr, value)

Другие вопросы по теме