# mementos /

Filename Size Date modified Message
101 B
4.3 KB
1.7 KB
1.6 KB

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.

## Usage

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


## Notes

• 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'


## Installation

pip install mementos


(You may need to prefix this with "sudo " to authorize installation.)