Classes that use this caching metaclass will have their instances automatically cached based on instantiation-time arguments (i.e. to __init__). Useful for not repetitively creating expensive-to-create objects, and for making sure that objects are created only once.
Say you have a class Thing that requires expensive computation to create, or that should be created only once. You can make that happen simply by adding one line to its definition:
from mementos import MementoMetaclass class Thing(object): __metaclass__ = MementoMetaclass def __init__(self, name): self.name = name ...
Then Thing objects will be memoized:
t1 = Thing("one") t2 = Thing("one") assert t1 is t2
Derived from an ActiveState recipe by Valentino Volonghi.
It is safe to memoize multiple classes at the same time. They will all be stored in the same cache, but their class is a part of the cache key, so the values are distinct.
This implementation is not thread-safe, in and of itself. If you're in a multi-threaded environment, wrap object instantiation in a lock.
Be careful with call signatures. This metaclass caches on call signature, which can vary if keyword args are used. E.g. def func(a, b=2) could be called func(1), func(1,2), func(a=1), func(1, b=2), or func(a=2, b=2)--and all resolve to the same logical call. And this is just for two parameters! If there are more than one kwarg, they can be arbitrarily ordered, creating many logically identical permuations. Thank Goodness Python doesn't allow kwargs to come before positional args, else there'd be even more ways to make the same call.
So if you instantiate an object once, then again with a logically identical call but using a different calling structure/signature, the object won't be created and cached just once--it will be created and cached multiple times.:o1 = Thing("lovely") o2 = Thing(name="lovely") assert o1 is not o2 # because the call signature is different
This may degrade performance, and can also create errors, if you're counting on mementos to create just one object. So don't do that. Use a consistent calling style, and it won't be a problem.
In most cases, this isn't an issue, because objects tend to be instanitated with a limited number of parameters, and you can take care that you instantiate them with parallel call signatures. Since this works 99% of the time and has a simple implementation, it's worth the price of this inelegance. For the 1% edge-cases where multiple call signature variations must be conclusively resolved to a unique canonical signature, the inspect module could be used (e.g. getargvalues() or getcallargs() to create such a unified key.
Not to be confused with the memento module, which does something completely different.
In some cases, you may not want the entire initialization-time call signature to define the cache key--only part of it. (Because some arguments to __init__ may be useful, but not really part of the object's 'identity'.) The best option is to design __init__ without superflous attributes, then create a second method that adds sets useful-but-not-essential data. E.g.:class OtherThing(object): __metaclass__ = MementoMetaclass def __init__(self, name): self.name = name self.color = None self.weight = None def set(self, color=None, weight=None): self.color = color or self.color self.weight = weight or self.weight return self ot1 = OtherThing("one").set(color='blue') ot2 = OtherThing("one").set(weight='light') assert ot1 is ot2 assert ot1.color == ot2.color == 'blue' assert ot1.weight == ot2.weight == 'light'
pip install mementos
(You may need to prefix this with "sudo " to authorize installation.)