Method Cache
------------

cachedIn
~~~~~~~~

The `cachedIn` property allows to specify the attribute where to store the
computed value:

    >>> import math
    >>> from zope.cachedescriptors import method

    >>> class Point(object):
    ... 
    ...     def __init__(self, x, y):
    ...         self.x, self.y = x, y
    ...
    ...     @method.cachedIn('_cache')
    ...     def distance(self, x, y):
    ...         print('computing distance')
    ...         return math.hypot(self.x - x, self.y - y)
    ...
    >>> point = Point(1.0, 2.0)   

The value is computed once:

    >>> point.distance(2, 2)
    computing distance
    1.0
    >>> point.distance(2, 2)
    1.0


Using different arguments calculates a new distance:

    >>> point.distance(5, 2)
    computing distance
    4.0
    >>> point.distance(5, 2)
    4.0


The data is stored at the given `_cache` attribute:

    >>> isinstance(point._cache, dict)
    True

    >>> sorted(point._cache.items())
    [(((2, 2), ()), 1.0), (((5, 2), ()), 4.0)]


It is possible to exlicitly invalidate the data:

    >>> point.distance.invalidate(point, 5, 2)
    >>> point.distance(5, 2)
    computing distance
    4.0

Invalidating keys which are not in the cache, does not result in an error:

    >>> point.distance.invalidate(point, 47, 11)


It is possible to pass in a factory for the cache attribute. Create another
Point class:


    >>> class MyDict(dict):
    ...     pass
    >>> class Point(object):
    ... 
    ...     def __init__(self, x, y):
    ...         self.x, self.y = x, y
    ...
    ...     @method.cachedIn('_cache', MyDict)
    ...     def distance(self, x, y):
    ...         print('computing distance')
    ...         return math.sqrt((self.x - x)**2 + (self.y - y)**2)
    ...
    >>> point = Point(1.0, 2.0)   
    >>> point.distance(2, 2)
    computing distance
    1.0

Now the cache is a MyDict instance:

    >>> isinstance(point._cache, MyDict)
    True
