У меня есть класс для создания спрайтов мухи, и я использую декоратор для вызова этого класса. Вот какой код:
class flyweight:
def __init__(self, cls):
self._cls = cls
self.__instances = dict()
def __call__(self, title):
return self.__instances.setdefault((title), self._cls(title))
В этом вопросе я просто упрощу код, чтобы показать, что актуально.
@flyweight
class Sprite:
def __init__(self, title, surf=None):
self.title = title
self.surf = surf if surf is not None else pygame.image.load('Images/Sprites/'+title+'.png').convert_alpha()
self.w, self.h = self.surf.get_size()
@staticmethod
def from_colour(colour, size=(40,40)):
surf = pygame.Surface(size).convert(); surf.fill(colour)
return Sprite(colour, surf)
red = Sprite.from_colour((125,0,0))
Но это дает мне ошибку:
AttributeError: 'flyweight' object has no attribute 'from_colour'
Должен ли я перестроить мою реализацию мухи или есть какой-то путь вокруг этого?
flyweight
декоратор действительно должен пройти через все конструкторы к базовому классу, включая @classmethod
и @staticmethod
альтернативные конструкторы. Фактически, в более общем плане, декоратор класса действительно должен сохранить обернутый класс для всего публичного интерфейса.
И, хотя мы могли бы легко модифицировать flyweight
чтобы пройти через остальную часть интерфейса Sprite
, что в данном случае является именно этим методом from_colour
, это будет болью для менее тривиального класса или для класса, который когда-либо изменяется. И действительно, какой смысл делать декоратор, который работает только с одним классом?
Итак, позвольте изменить его на:
Возьмите любую подпись конструктора. В идеале мы хотели бы настроить его на то, какая часть подписи подсчитывается как ключ 1, но чтобы вещи не усложнялись, просто исправьте это как первый аргумент.
Пройдите через весь открытый интерфейс класса, а не только его интерфейс __call__
.
Так:
class flyweight:
def __init__(self, cls):
self._cls = cls
self.__instances = dict()
def __call__(self, key, *args, **kw):
return self.__instances.setdefault(key, self._cls(key, *args, **kw))
def __getattr__(self, name):
if not name.startswith('_'):
return getattr(self._cls, name)
1. В какой-то другой библиотеке, которую я использовал, для этого есть приятный дизайн для кэшей памяти функций.Наверное, cachetools
.И это должно быть так же важно для классовых кладов.
После оформления имя обернутого объекта автоматически указывает на возвращаемые результаты декоратора. В этом случае Sprite
теперь хранит экземпляр flyweight
, который по очереди содержит атрибут, хранящий экземпляр исходного упакованного класса Sprite
. Например, печать Sprite
после объявлений дает: <__main__.flyweight object at 0x102373080>
. Однако staticmethod
from_colour
можно вызвать из _cls
:
red = Sprite._cls.from_colour((125,0,0))
from_colour
должен был быть частью публичного интерфейса Sprite
. Но вместо этого теперь вы должны вызывать его через закрытый атрибут. Хуже того, приватный атрибут класса, о котором пользователи Sprite
не должны знать. Тем более, что вы должны иметь возможность @flyweight
и возвращать его обратно, не изменяя ничего, кроме производительности, и вы определенно не сможете, если это затеняет интерфейс украшенного класса.