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 | """ |
---|
8 | decorators that cache results to memory, to file, or to a database |
---|
9 | """ |
---|
10 | from functools import update_wrapper, wraps as functools_wraps |
---|
11 | from klepto.rounding import deep_round, simple_round |
---|
12 | from klepto.archives import cache as archive_dict |
---|
13 | from klepto.keymaps import stringmap |
---|
14 | from klepto import inf_cache |
---|
15 | from 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 |
---|
23 | memoized = inf_cache |
---|
24 | |
---|
25 | class 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 |
---|