snakeoil.klass.memoize module

Transparent opportunistic weakref instance caching at instantiation time

Using these classes allows producing singletons from a class definition. If an instance of a class is still in memory, created with the same positional arguments and keyword arguments, that instance will be returned.

This should only be used with classes that present an immutable shape. Anything mutable should use a registry pattern instead. By nature of this mechanism you cannot know if you will get a pre-existing instance from memory or will get a newly created instance. These instances are not pinned in memory- they’re weakly referenced. Thus it’s strongly advised you use this only with instances that are effectively immutable.

The instance reuse that this allows- a weak reference memoization- allows complex object creation and whatever expensive actions necessary for that instance, to be effectively memoized opportunistically.

For the correct architectural patterns, this can drastically both reduce memory usage and reduce runtime. It’s opportunistic reuse of previous work an instance may have done for things like parsing, or generating chains of other immutable objects- parse byproducts.

Note: this is built on python’s weak references. It will not hold an instance in memory. It cannot optimize reuse where instances are quickly created and destroyed; this should be considered for scenarios where objects encapsulate things that live for longer period of times.

Whilst python provide class level __new__, returned instances from that by the class still have __init__ ran- including for pre-existing instances. This implementation does not have that problem via leveraging a metaclass.

To use this functionality, either inherit WeaklyCached for classes that have no custom metaclasses in use, or if you use ABC inherit WeaklyCachedABC, or if you have other metaclasses- you can create your own mixup of metaclasses via WeaklyCachedMeta.

If the class call has a kwarg of disable_inst_caching=True, then that explicitly disable this functionality for that specific instance. This should be used when you know that some component of the args are mutable- or unhashable- at time of the instance creation.

Example usage:

>>> from snakeoil.klass.caching import WeaklyCached
>>> class myfoo(WeaklyCached):
...   counter = 0
...
...   def __init__(self, arg1, arg2, option=None):
...     self.arg1, self.arg2, self.option = arg1, arg2, option
...     self.__class__.counter += 1
>>>
>>> assert myfoo(1, 2, 3) is myfoo(1, 2, 3)
>>> assert myfoo(1, 2, option=3) is myfoo(1, 2, option=3)
>>> assert myfoo(1, 2) is not myfoo(1, 2, 3)
>>> assert myfoo(1, 2) is not myfoo(1, 2, disable_inst_caching=True)

Finally, subclasses can disable all caching of this classes instances via passing caching=False in the class definition. caching=True is the default.

>>> from snakeoil.klass.caching import WeaklyCached
>>> class foo(WeaklyCached): ...
>>> class foo2(foo, caching=False): ...
>>>
>>> assert foo2() is not foo2()
class snakeoil.klass.memoize.WeaklyCached(*args: Hashable, disable_inst_caching=False, **kwargs: Hashable)[source]

Bases: object

Reuse existing instances in memory if available.

This works via hashing the positional and keyword args of an instance creation, and using that as a key for looking up any previouse instantiation that are still held in memory.

This explicitly adds a slotted ‘__weakref__’ attribute. If you inherit this class, you do not need to add it yourself.

Parent class settings are inherited by the child, unless you explicitly override it. IE, if you tolerate uncachable arguments in the parent, the children all tolerate it unless they state they don’t.

If the parent disables caching, the children have caching disabled unless they re-enable it.

Parameters:
  • caching – if True (the default), all instances of this class will be cached. If ever set to False, children classes have caching disabled until they explicitly re-enable it.

  • tolerate_uncachable_args

    if True, calls with unhashable args or kwargs will trigger a warning, but will be allowed. The instance will not be cached or reusable. If False- the default- they will result in a TypeError since the instance isn’t cachable.

    This inherits the parent’s setting, unless explicitly overridden.

    Setting this to True should only be used for transitioning a class to being fully cachable.

class snakeoil.klass.memoize.WeaklyCachedABC(*args: Hashable, disable_inst_caching=False, **kwargs: Hashable)[source]

Bases: WeaklyCached

Derivative of WeaklyCached that addresses the ABC metaclass conflict.

If you use ABC for the inheritance chain leading to the class that you wish to weakly cache, use this.

class snakeoil.klass.memoize.WeaklyCachedABCMeta(name, bases, namespace, /, **kwargs)[source]

Bases: WeaklyCachedMeta, ABCMeta

Pre-mixed WeaklyCachedMeta with ABC’s metaclass

Use WeaklyCachedABC instead of this metaclass directly.

class snakeoil.klass.memoize.WeaklyCachedMeta[source]

Bases: type

Metaclass implementation used for weakly cached instance reuse

Use WeaklyCached instead for your implementations. This should only be used when you’re having to intermix metaclasses.