Я проводил рефакторинг текстового класса, предназначенного для вывода текста на экран.
Недавно я узнал о декораторе @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
. Хотя после некоторых поисков и не найдя ни у кого такой же проблемы, я считаю, что моя логика может быть ошибочной, и существует более простой способ определить, когда обновляются свойства объекта, и запустить некоторый код.
@chepner Я бы посчитал это ответом, чтобы вы могли опубликовать его как ответ на вопрос. Также чтение документации было очень интересным, спасибо за предложение.
Я не считаю это ответом, потому что это просто написание кода для вас :) Я предоставил ссылку, чтобы вы могли сначала попробовать написать ее самостоятельно и, если необходимо, задать более конкретный вопрос. (Когда этот вопрос находился в стадии постановки, я предложил сделать это вместо того, чтобы одобрять вопрос для публикации, поскольку я чувствовал, что он слишком широк. Другие не согласились и одобрили его, и он был - что неудивительно для меня - почти сразу же отклонен.)
@chepner Как вы думаете, мне следует переписать весь вопрос, зная, что ответ будет более конкретным? Потому что в этом случае что-то очень похожее уже было задано, так что это будет просто косвенный дубликат.
Да, в идеале использовать те же механизмы, что и сам 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)
Как я предлагал, прежде чем кто-то проголосовал за публикацию этого вопроса, я предлагаю прочитать docs.python.org/3/library/pathlib.html, чтобы вы могли увидеть, как
property
реализует протокол дескриптора и как вы можете реализовать свой собственный дескрипторRenderable
чтобы вы могли писатьtext = Renderable()
вместо того, чтобы создаватьtext
свойство.