Skip to main content

MemoizingDecorator — wiki

This is a function that creates a memoizing decorator that uses the given dictionary type. It was developed for use with pygame, and gaming applications in general, as it allows a resource (such as an image) to be loaded once and once only. Typically it should be used with a WeakValueDictionary so that unreferenced resources are released rather than causing memory leaks.

def memoizer(new_dict=dict):
    """
    Creates a memoizer with the given dictionary policy. 

    The memoizer thus obtained can be used as a decorator to remember the
    results of calls to long-running functions, such as loading images
    from disk. It also means that there will only be one copy of the image,
    which can be quite handy when dealing with restricted resources.

    Example:

    weak_memoize = memoize(new_dict=weakref.WeakValueDictionary)

    @weak_memoize
    def load_image(filename):
        # Your long running image-loading code goes here.
        return result
        
    """
    def memoize(func):
        cache = new_dict()

        def memo(*args, **kwargs):
            try:
                # Create a key, resorting to repr if the key isn't hashable.
                try:
                    k = (args, tuple(kwargs.items()))
                    hash(k)
                except TypeError:
                    k = repr(k)
                    
                # Try to return the result from the cache.
                return cache[k]
            except KeyError:
                # The key wasn't found, so invoke the function and save the
                # result in the cache.
                result = func(*args, **kwargs)
                cache[k] = result
                return result
            
        return memo
    
    return memoize

Here's an example image loader that uses it, both to load an entire image, then to create subsurfaces from that image.

"""
# 'all_sprites.png' is loaded once, by the following line.
man1 = load_image('all_sprites.png', (1, 12, 32, 32), -1)

# 'all_sprites.png' is not loaded by this line as it has already
# been loaded once.
man2 = load_image('all_sprites.png', (33, 12, 32, 32), -1)

# The following line returns the exact same data as man1, as it
# has the same parameters. The data comes straight from the cache
# and is not reloaded.
man3 = load_image('all_sprites.png', (1, 12, 32, 32), -1)
"""

import weakref
memoize = memoizer(new_dict=weakref.WeakValueDictionary)


@memoize
def load_entire_image(name):
    """Loads the image with the given name."""

    fullname = os.path.join('sprites', name)
    try:
        image = pygame.image.load(fullname)
    except pygame.error, message:
        print 'Cannot load image:', fullname
        raise SystemExit, message
    image = image.convert()
    return image


@memoize
def load_image(name, rect=None, colorkey=None):
    """Returns a rectangular portion of a named image."""

    image = load_entire_image(name)
    if rect is not None:
        image = image.subsurface(Rect(rect))
    if colorkey is -1:
        colorkey = image.get_at((0,0))
    image.set_colorkey(colorkey)
    return image