]>
Commit | Line | Data |
---|---|---|
e0df8241 JR |
1 | import types |
2 | import functools | |
3 | ||
4 | ||
5 | # from jaraco.functools 3.3 | |
6 | def method_cache(method, cache_wrapper=None): | |
7 | """ | |
8 | Wrap lru_cache to support storing the cache data in the object instances. | |
9 | ||
10 | Abstracts the common paradigm where the method explicitly saves an | |
11 | underscore-prefixed protected property on first call and returns that | |
12 | subsequently. | |
13 | ||
14 | >>> class MyClass: | |
15 | ... calls = 0 | |
16 | ... | |
17 | ... @method_cache | |
18 | ... def method(self, value): | |
19 | ... self.calls += 1 | |
20 | ... return value | |
21 | ||
22 | >>> a = MyClass() | |
23 | >>> a.method(3) | |
24 | 3 | |
25 | >>> for x in range(75): | |
26 | ... res = a.method(x) | |
27 | >>> a.calls | |
28 | 75 | |
29 | ||
30 | Note that the apparent behavior will be exactly like that of lru_cache | |
31 | except that the cache is stored on each instance, so values in one | |
32 | instance will not flush values from another, and when an instance is | |
33 | deleted, so are the cached values for that instance. | |
34 | ||
35 | >>> b = MyClass() | |
36 | >>> for x in range(35): | |
37 | ... res = b.method(x) | |
38 | >>> b.calls | |
39 | 35 | |
40 | >>> a.method(0) | |
41 | 0 | |
42 | >>> a.calls | |
43 | 75 | |
44 | ||
45 | Note that if method had been decorated with ``functools.lru_cache()``, | |
46 | a.calls would have been 76 (due to the cached value of 0 having been | |
47 | flushed by the 'b' instance). | |
48 | ||
49 | Clear the cache with ``.cache_clear()`` | |
50 | ||
51 | >>> a.method.cache_clear() | |
52 | ||
53 | Same for a method that hasn't yet been called. | |
54 | ||
55 | >>> c = MyClass() | |
56 | >>> c.method.cache_clear() | |
57 | ||
58 | Another cache wrapper may be supplied: | |
59 | ||
60 | >>> cache = functools.lru_cache(maxsize=2) | |
61 | >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache) | |
62 | >>> a = MyClass() | |
63 | >>> a.method2() | |
64 | 3 | |
65 | ||
66 | Caution - do not subsequently wrap the method with another decorator, such | |
67 | as ``@property``, which changes the semantics of the function. | |
68 | ||
69 | See also | |
70 | http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ | |
71 | for another implementation and additional justification. | |
72 | """ | |
73 | cache_wrapper = cache_wrapper or functools.lru_cache() | |
74 | ||
75 | def wrapper(self, *args, **kwargs): | |
76 | # it's the first call, replace the method with a cached, bound method | |
77 | bound_method = types.MethodType(method, self) | |
78 | cached_method = cache_wrapper(bound_method) | |
79 | setattr(self, method.__name__, cached_method) | |
80 | return cached_method(*args, **kwargs) | |
81 | ||
82 | # Support cache clear even before cache has been created. | |
83 | wrapper.cache_clear = lambda: None | |
84 | ||
85 | return wrapper | |
86 | ||
87 | ||
88 | # From jaraco.functools 3.3 | |
89 | def pass_none(func): | |
90 | """ | |
91 | Wrap func so it's not called if its first param is None | |
92 | ||
93 | >>> print_text = pass_none(print) | |
94 | >>> print_text('text') | |
95 | text | |
96 | >>> print_text(None) | |
97 | """ | |
98 | ||
99 | @functools.wraps(func) | |
100 | def wrapper(param, *args, **kwargs): | |
101 | if param is not None: | |
102 | return func(param, *args, **kwargs) | |
103 | ||
104 | return wrapper |