from functools import wraps def cached_class(klass): """Decorator to cache class instances by constructor arguments. We "tuple-ize" the keyword arguments dictionary since dicts are mutable; keywords themselves are strings and so are always hashable, but if any arguments (keyword or positional) are non-hashable, that set of arguments is not cached. """ cache = {} @wraps(klass, assigned=('__name__', '__module__'), updated=()) class _decorated(klass): # The wraps decorator can't do this because __doc__ # isn't writable once the class is created __doc__ = klass.__doc__ def __new__(cls, *args, **kwds): key = (cls,) + args + tuple(kwds.iteritems()) try: inst = cache.get(key, None) except TypeError: # Can't cache this set of arguments inst = key = None if inst is None: # Technically this is cheating, but it works, # and takes care of initializing the instance # (so we can override __init__ below safely); # calling up to klass.__new__ would be the # "official" way to create the instance, but # that raises DeprecationWarning if there are # args or kwds and klass does not override # __new__ (which most classes don't), because # object.__new__ takes no parameters (and in # Python 3 the warning will become an error) inst = klass(*args, **kwds) # This makes isinstance and issubclass work # properly inst.__class__ = cls if key is not None: cache[key] = inst return inst def __init__(self, *args, **kwds): # This will be called every time __new__ is # called, so we skip initializing here and do # it only when the instance is created above pass return _decorated