1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
20 from __future__ import absolute_import # so we can import standard 'collections' and 'threading'
22 from threading import Lock
23 from functools import partial
25 from .collections import OrderedDict
28 class cachedmethod(object): # pylint: disable=invalid-name
30 Decorator for caching method return values.
32 The implementation is thread-safe.
34 Supports ``cache_info`` to be compatible with Python 3's ``functools.lru_cache``. Note that the
35 statistics are combined for all instances of the class.
37 Won't use the cache if not called when bound to an object, allowing you to override the cache.
39 Adapted from `this solution
40 <http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/>`__.
45 def __init__(self, func):
46 self.__doc__ = func.__doc__
54 return (self.hits, self.misses, None, self.misses)
56 def reset_cache_info(self):
61 def __get__(self, instance, owner):
63 # Don't use cache if not bound to an object
64 # Note: This is also a way for callers to override the cache
66 return partial(self, instance)
68 def __call__(self, *args, **kwargs):
70 return self.func(*args, **kwargs)
73 if not hasattr(instance, '_method_cache'):
74 instance._method_cache = {}
75 method_cache = instance._method_cache
77 key = (self.func, args[1:], frozenset(kwargs.items()))
81 return_value = method_cache[key]
84 return_value = self.func(*args, **kwargs)
86 method_cache[key] = return_value
88 # Another thread may override our cache entry here, so we need to read
89 # it again to make sure all threads use the same return value
90 return_value = method_cache.get(key, return_value)
95 class HasCachedMethods(object):
97 Provides convenience methods for working with :class:`cachedmethod`.
100 def __init__(self, method_cache=None):
101 self._method_cache = method_cache or {}
104 def _method_cache_info(self):
106 The cache infos of all cached methods.
108 :rtype: dict of str, 4-tuple
111 cached_info = OrderedDict()
112 for k, v in self.__class__.__dict__.iteritems():
113 if isinstance(v, property):
114 # The property getter might be cached
116 if hasattr(v, 'cache_info'):
117 cached_info[k] = v.cache_info()
120 def _reset_method_cache(self):
122 Resets the caches of all cached methods.
125 if hasattr(self, '_method_cache'):
126 self._method_cache = {}
128 # Note: Another thread may already be storing entries in the cache here.
129 # But it's not a big deal! It only means that our cache_info isn't
130 # guaranteed to be accurate.
132 for entry in self.__class__.__dict__.itervalues():
133 if isinstance(entry, property):
134 # The property getter might be cached
136 if hasattr(entry, 'reset_cache_info'):
137 entry.reset_cache_info()