cache with least accessed items eviction I wrote this code, in order to implement a cache decorator which handles least accessed eviction.
The role of this decorator is to memoize decorated functions calls with args and kwargs and return the previously computed value if still in cache.
Warning, this handles only serializable args and kwargs, for the sake of simplicity.
Please review it and tell me if this makes sense.
The code:
import json
import hashlib
from collections import OrderedDict


class EvictionCache(OrderedDict):

    def __init__(self, *args, **kwargs):
        max_size = kwargs.pop('max_size', None)
        super(EvictionCache, self).__init__(*args, **kwargs)
        self._max_size = max_size

    @property
    def full(self):
        return len(self) &gt;= self._max_size

    def set(self, hash, value):
        if self.full:
            self.evict()
        super(EvictionCache, self).__setitem__(hash, value)

    def get(self, hash):
        value = super(EvictionCache, self).pop(hash, None)
        if value:
            super(EvictionCache, self).__setitem__(hash, value)
        return value

    def evict(self):
        print 'evicting oldest cached item'
        super(EvictionCache, self).popitem(last=False)


def cache(max_size=None):

    def make_md5(args, kwargs):
        data = dict(args=args, kwargs=kwargs)
        md5 = hashlib.md5(json.dumps(data, sort_keys=True)).hexdigest()
        return md5

    def wrapper(f):
        # make this cache belong to the decorated function to prevent the cache
        # to be shared between different decorated functions
        f._cache = EvictionCache(max_size=max_size)

        def inner(*args, **kwargs):
            md5 = make_md5(args, kwargs)
            res = f._cache.get(md5)
            if res:
                print 'from cache'
                return res

            res = f(*args, **kwargs)
            print 'to cache'
            f._cache.set(md5, res)

            return res

        return inner
    return wrapper


@cache(max_size=2)
def func(x):
    return x * 2


if __name__ == '__main__':

    print func(1)
    print func(2)
    print func(2)
    print func(1)
    print func(2)
    print func(3)
    print func(4)
    print func(5)

output
to cache
2
to cache
4
from cache
4
from cache
2
from cache
4
to cache
evicting oldest cached item
6
to cache
evicting oldest cached item
8
to cache
evicting oldest cached item
10
I don't really feel subclassing a built-in data structure and defining your custom get() and set() methods is a good idea. One of the downsides of the current implementation is that if I would do self.cache[key] = value - this will not go through the cache's set() method and, hence, will not go through the "eviction" check at all. If you are subclassing a dictionary or an ordered dictionary, properly define the __getitem__() and __setitem__() magic methods instead of writing your custom get() and set() on top of it.

Also check this implementation that has a simpler wrapper, but uses a "proxy" "cached function" helper class.

And, here is the source code of the Python's functools.lru_cache for the reference.

Some other minor notes:


use print() as a function for Python-3.x compatibility