A class decorator that ensures that only one instance of the class exists for each distinct set of constructor arguments.
Note that if a decorated class is subclassed, each subclass is cached separately. (This is because each cached subclass is a different cls
argument to the __new__
method.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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
|
This is similar to caching a function by arguments, but there are a couple of extra wrinkles for classes, which this recipe takes care of.