1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 import collections, weakref
35 from functools import reduce
36 from pickle import PickleError
37 from decorator import decorate
38
39 from .core.observable import Observable
40 from numbers import Number
43 - def __init__(self, operation, limit=3, ignore_args=(), force_kwargs=(), cacher_enabled=True):
44 """
45 Cache an `operation`. If the operation is a bound method we will
46 create a cache (FunctionCache) on that object in order to keep track
47 of the caches on instances.
48
49 Warning: If the instance already had a Cacher for the operation
50 that Cacher will be overwritten by this Cacher!
51
52 :param callable operation: function to cache
53 :param int limit: depth of cacher
54 :param [int] ignore_args: list of indices, pointing at arguments to ignore in `*args` of `operation(*args)`. This includes self, so make sure to ignore self, if it is not cachable and you do not want this to prevent caching!
55 :param [str] force_kwargs: list of kwarg names (strings). If a kwarg with that name is given, the cacher will force recompute and wont cache anything.
56 :param int verbose: verbosity level. 0: no print outs, 1: casual print outs, 2: debug level print outs
57 """
58 self.limit = int(limit)
59 self.ignore_args = ignore_args
60 self.force_kwargs = force_kwargs
61 self.operation = operation
62 self.cacher_enabled = cacher_enabled
63 if getattr(self.operation, '__self__', None) is not None:
64 obj = self.operation.__self__
65 if not hasattr(obj, 'cache'):
66 obj.cache = FunctionCache()
67 cache = obj.cache
68 self.cacher_enabled = (self.cacher_enabled and cache.caching_enabled)
69
70
71 cache[self.operation] = self
72
73 self.cached_input_ids = {}
74
75 self.reset()
76
78 "Disable the caching of this cacher. This also removes previously cached results"
79 self.cacher_enabled = False
80 self.reset()
81
83 "Enable the caching of this cacher."
84 self.cacher_enabled = True
85
87 """returns the self.id of an object, to be used in caching individual self.ids"""
88 return hex(id(obj))
89
95
97 "get the cacheid (conc. string of argument self.ids in order)"
98 cache_id = "".join(self.id(a) for a in combined_args_kw)
99 return cache_id
100
102 "Ensures the cache is within its limits and has one place free"
103 if len(self.order) == self.limit:
104
105 cache_id = self.order.popleft()
106 combined_args_kw = self.cached_inputs[cache_id]
107 for ind in combined_args_kw:
108 ind_id = self.id(ind)
109 tmp = self.cached_input_ids.get(ind_id, None)
110 if tmp is not None:
111 ref, cache_ids = tmp
112 if len(cache_ids) == 1 and ref() is not None:
113 ref().remove_observer(self, self.on_cache_changed)
114 del self.cached_input_ids[ind_id]
115 else:
116 cache_ids.remove(cache_id)
117 self.cached_input_ids[ind_id] = [ref, cache_ids]
118 try:
119 del self.cached_outputs[cache_id]
120 except KeyError:
121
122 pass
123 try:
124 del self.inputs_changed[cache_id]
125 except KeyError:
126
127 pass
128 try:
129 del self.cached_inputs[cache_id]
130 except KeyError:
131
132 pass
133
135 """This adds cache_id to the cache, with inputs and output"""
136 self.inputs_changed[cache_id] = False
137 self.cached_outputs[cache_id] = output
138 self.order.append(cache_id)
139 self.cached_inputs[cache_id] = inputs
140 for a in inputs:
141 if a is not None and not isinstance(a, Number) and not isinstance(a, str):
142 ind_id = self.id(a)
143 v = self.cached_input_ids.get(ind_id, [weakref.ref(a), []])
144 v[1].append(cache_id)
145 if len(v[1]) == 1:
146 a.add_observer(self, self.on_cache_changed)
147 self.cached_input_ids[ind_id] = v
148
150 """
151 A wrapper function for self.operation,
152 """
153
154
155 if not self.cacher_enabled:
156 return self.operation(*args, **kw)
157
158
159
160 if len(self.force_kwargs) != 0:
161 for k in self.force_kwargs:
162 if k in kw and kw[k] is not None:
163 return self.operation(*args, **kw)
164
165
166 inputs = self.combine_inputs(args, kw, self.ignore_args)
167 cache_id = self.prepare_cache_id(inputs)
168
169 if reduce(lambda a, b: a or (not (isinstance(b, Observable) or b is None or isinstance(b, Number) or isinstance(b, str))), inputs, False):
170
171
172 return self.operation(*args, **kw)
173
174 not_seen = not(cache_id in self.inputs_changed)
175 changed = (not not_seen) and self.inputs_changed[cache_id]
176 if changed or not_seen:
177
178 try:
179 new_output = self.operation(*args, **kw)
180 except:
181 self.reset()
182 raise
183 if(changed):
184
185 self.inputs_changed[cache_id] = False
186 self.cached_outputs[cache_id] = new_output
187 else:
188
189 self.ensure_cache_length()
190 self.add_to_cache(cache_id, inputs, new_output)
191
192 return self.cached_outputs[cache_id]
193
195 """
196 A callback funtion, which sets local flags when the elements of some cached inputs change
197
198 this function gets 'hooked up' to the inputs when we cache them, and upon their elements being changed we update here.
199 """
200 for what in [direct, which]:
201 ind_id = self.id(what)
202 _, cache_ids = self.cached_input_ids.get(ind_id, [None, []])
203 for cache_id in cache_ids:
204 self.inputs_changed[cache_id] = True
205
207 """
208 Totally reset the cache
209 """
210 [a().remove_observer(self, self.on_cache_changed) if (a() is not None) else None for [a, _] in self.cached_input_ids.values()]
211
212 self.order = collections.deque()
213 self.cached_inputs = {}
214
215
216
217
218
219 self.cached_input_ids = {}
220
221
222 self.cached_outputs = {}
223 self.inputs_changed = {}
224
226 return Cacher(self.operation, self.limit, self.ignore_args, self.force_kwargs)
227
229 raise PickleError("Trying to pickle Cacher object with function {}, pickling functions not possible.".format(str(self.operation)))
230
232 raise PickleError("Trying to pickle Cacher object with function {}, pickling functions not possible.".format(str(self.operation)))
233
234 @property
237
238 - def __str__(self, *args, **kwargs):
239 return "Cacher({})\n limit={}\n \#cached={}".format(self.__name__, self.limit, len(self.cached_input_ids))
240
243 dict.__init__(self, *args, **kwargs)
244 self.caching_enabled = True
245
247 "Disable the cache of this object. This also removes previously cached results"
248 self.caching_enabled = False
249 for c in self.values():
250 c.disable_cacher()
251
253 "Enable the cache of this object."
254 self.caching_enabled = True
255 for c in self.values():
256 c.enable_cacher()
257
259 "Reset (delete) the cache of this object"
260 for c in self.values():
261 c.reset()
262
264 """
265 A decorator which can be applied to bound methods in order to cache them
266 """
267 - def __init__(self, limit=5, ignore_args=(), force_kwargs=()):
268 self.limit = limit
269 self.ignore_args = ignore_args
270 self.force_kwargs = force_kwargs
271 self.f = None
273 self.f = f
274 def g(obj, *args, **kw):
275 obj = args[0]
276 if not hasattr(obj, 'cache'):
277 obj.cache = FunctionCache()
278 cache = obj.cache
279 if self.f in cache:
280 cacher = cache[self.f]
281 else:
282 cacher = cache[self.f] = Cacher(self.f, self.limit, self.ignore_args, self.force_kwargs, cacher_enabled=cache.caching_enabled)
283 return cacher(*args, **kw)
284 g.__name__ = f.__name__
285 g.__doc__ = f.__doc__
286 g = decorate(self.f, g)
287 return g
288