source: branches/decorate/memoize.py @ 855

Revision 855, 4.0 KB checked in by mmckerns, 5 months ago (diff)

updated copyright to 2016

Line 
1#!/usr/bin/env python
2#
3# Author: Mike McKerns (mmckerns @caltech and @uqfoundation)
4# Copyright (c) 2012-2016 California Institute of Technology.
5# License: 3-clause BSD.  The full license text is available at:
6#  - http://mmckerns.github.io/project/mystic/browser/mystic/LICENSE
7"""
8decorators that cache results to memory, to file, or to a database
9"""
10from functools import update_wrapper, wraps as functools_wraps
11from klepto.rounding import deep_round, simple_round
12from klepto.archives import cache as archive_dict
13from klepto.keymaps import stringmap
14from klepto import inf_cache
15from klepto.tools import CacheInfo
16
17__all__ = ['memoize', 'memoized']
18
19#XXX: use cache maxsize algorithms... where dump to archive if maxsize
20#XXX: memoized fails when decorating a class method ???
21
22# backward compatibility
23memoized = inf_cache
24
25class memoize(object):
26    """This is safecache.inf_cache implemented as a class-based decorator.
27
28    Decorator that memoizes a function's return value each time it is called.
29    If called later with the same arguments, the memoized value is returned, and
30    not re-evaluated.  This may lead to memory issues, as cache is not cleared.
31    Can memoize a *method* on an object.
32    """
33    def __init__(self, cache=None, keymap=None, tol=None, deep=False):
34        self.__maxsize = None
35        if keymap is None: keymap = stringmap(flat=False)
36        if cache is None: cache = archive_dict()
37        elif type(cache) is dict: cache = archive_dict(cache)
38        self.__cache = cache
39        self.__keymap = keymap
40
41        if deep: rounded = deep_round
42        else: rounded = simple_round
43
44        @rounded(tol)
45        def rounded_args(*args, **kwds):
46            return (args, kwds)
47        self.__rounded_args = rounded_args
48        return
49
50    def __call__(self, user_function):  # i.e. 'decorating_function'
51        stats = [0, 0, 0]               # make statistics updateable non-locally
52        HIT, MISS, LOAD = 0, 1, 2       # names for the stats fields
53
54        def wrapper(*args, **kwds):
55            try:
56                _args, _kwds = self.__rounded_args(*args, **kwds)
57                key = self.__keymap(*_args, **_kwds)
58            except: #TypeError
59                result = user_function(*args, **kwds)
60                return result
61
62            try:
63                # get cache entry
64                result = self.__cache[key]
65                stats[HIT] += 1
66            except KeyError:
67                # if not in cache, look in archive
68                if self.__cache.archived():
69                    self.__cache.load(key)
70                try:
71                    result = self.__cache[key]
72                    stats[LOAD] += 1
73                except KeyError:
74                    # if not found, then compute
75                    result = user_function(*args, **kwds)
76                    self.__cache[key] = result
77                    stats[MISS] += 1
78            return result
79
80        def archive(obj):
81            """Replace the cache archive"""
82            self.__cache.archive = obj
83
84        def __get_cache():
85            """Get the cache"""
86            return self.__cache
87
88        def clear(keepstats=False):
89            """Clear the cache and statistics"""
90            self.__cache.clear()
91            if not keepstats: stats[:] = [0, 0, 0]
92
93        def info():
94            """Report cache statistics"""
95            return CacheInfo(stats[HIT], stats[MISS], stats[LOAD], self.__maxsize, len(self.__cache))
96
97        # interface
98        wrapper.__wrapped__ = user_function
99        wrapper.info = info
100        wrapper.clear = clear
101        wrapper.load = self.__cache.load
102        wrapper.dump = self.__cache.dump
103        wrapper.archive = archive
104        wrapper.archived = self.__cache.archived
105        wrapper.__cache__ = __get_cache
106        return update_wrapper(wrapper, user_function)
107
108#   def __repr__(self):
109#       """Return the function's docstring."""
110#       return self.func.__doc__
111
112    def __get__(self, obj, objtype):
113        """Support instance methods."""
114        import functools
115        return functools.partial(self.__call__, obj)
116
117# EOF
Note: See TracBrowser for help on using the repository browser.