pythonpython-3.xdecoratorpython-decoratorsflyweight-pattern

Error when using staticmethod with flyweight decorator


I have a class for making sprites flyweight and I am using a decorator to call this class. Here is some code:

class flyweight:
    def __init__(self, cls):
        self._cls = cls
        self.__instances = dict()

    def __call__(self, title):
        return self.__instances.setdefault((title), self._cls(title))

In this question I'll just simplify the code to show what is relevant.

@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))

But this gives me the error:

AttributeError: 'flyweight' object has no attribute 'from_colour'

Should I remodel my flyweight implementation or is there some way around this?


Solution

  • A flyweight decorator really ought to pass through all constructors to the underlying class, including @classmethod and @staticmethod alternate constructors. In fact, more generally, a class decorator really ought to preserve the wrapped class's entire public interface.

    And, while we could easily modify flyweight to specifically pass through the rest of the Sprite interface, which in this case is just that from_colour method, that would be a pain for a less trivial class, or for a class that ever changes. And really, what's the point of making a decorator that only works with a single class?

    So, let's change it to:

    So:

    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. Some other library I've used has a nice design for this for function memo caches. Probably cachetools. And it ought to make just as much sense for class construction caches.