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