1
|
|
|
""" |
2
|
|
|
Tools for memoization of function results. |
3
|
|
|
""" |
4
|
|
|
from functools import wraps |
5
|
|
|
from six import iteritems |
6
|
|
|
from weakref import WeakKeyDictionary |
7
|
|
|
|
8
|
|
|
|
9
|
|
|
class lazyval(object): |
10
|
|
|
"""Decorator that marks that an attribute of an instance should not be |
11
|
|
|
computed until needed, and that the value should be memoized. |
12
|
|
|
|
13
|
|
|
Example |
14
|
|
|
------- |
15
|
|
|
|
16
|
|
|
>>> from zipline.utils.memoize import lazyval |
17
|
|
|
>>> class C(object): |
18
|
|
|
... def __init__(self): |
19
|
|
|
... self.count = 0 |
20
|
|
|
... @lazyval |
21
|
|
|
... def val(self): |
22
|
|
|
... self.count += 1 |
23
|
|
|
... return "val" |
24
|
|
|
... |
25
|
|
|
>>> c = C() |
26
|
|
|
>>> c.count |
27
|
|
|
0 |
28
|
|
|
>>> c.val, c.count |
29
|
|
|
('val', 1) |
30
|
|
|
>>> c.val, c.count |
31
|
|
|
('val', 1) |
32
|
|
|
>>> c.val = 'not_val' |
33
|
|
|
Traceback (most recent call last): |
34
|
|
|
... |
35
|
|
|
AttributeError: Can't set read-only attribute. |
36
|
|
|
>>> c.val |
37
|
|
|
'val' |
38
|
|
|
""" |
39
|
|
|
def __init__(self, get): |
40
|
|
|
self._get = get |
41
|
|
|
self._cache = WeakKeyDictionary() |
42
|
|
|
|
43
|
|
|
def __get__(self, instance, owner): |
44
|
|
|
if instance is None: |
45
|
|
|
return self |
46
|
|
|
try: |
47
|
|
|
return self._cache[instance] |
48
|
|
|
except KeyError: |
49
|
|
|
self._cache[instance] = val = self._get(instance) |
50
|
|
|
return val |
51
|
|
|
|
52
|
|
|
def __set__(self, instance, value): |
53
|
|
|
raise AttributeError("Can't set read-only attribute.") |
54
|
|
|
|
55
|
|
|
def __delitem__(self, instance): |
56
|
|
|
del self._cache[instance] |
57
|
|
|
|
58
|
|
|
|
59
|
|
|
class classlazyval(lazyval): |
60
|
|
|
""" Decorator that marks that an attribute of a class should not be |
61
|
|
|
computed until needed, and that the value should be memoized. |
62
|
|
|
|
63
|
|
|
Example |
64
|
|
|
------- |
65
|
|
|
|
66
|
|
|
>>> from zipline.utils.memoize import classlazyval |
67
|
|
|
>>> class C(object): |
68
|
|
|
... count = 0 |
69
|
|
|
... @classlazyval |
70
|
|
|
... def val(cls): |
71
|
|
|
... cls.count += 1 |
72
|
|
|
... return "val" |
73
|
|
|
... |
74
|
|
|
>>> C.count |
75
|
|
|
0 |
76
|
|
|
>>> C.val, C.count |
77
|
|
|
('val', 1) |
78
|
|
|
>>> C.val, C.count |
79
|
|
|
('val', 1) |
80
|
|
|
""" |
81
|
|
|
# We don't reassign the name on the class to implement the caching because |
82
|
|
|
# then we would need to use a metaclass to track the name of the |
83
|
|
|
# descriptor. |
84
|
|
|
def __get__(self, instance, owner): |
85
|
|
|
return super(classlazyval, self).__get__(owner, owner) |
86
|
|
|
|
87
|
|
|
|
88
|
|
|
def remember_last(f): |
89
|
|
|
""" |
90
|
|
|
Decorator that remembers the last computed value of a function and doesn't |
91
|
|
|
recompute it when called with the same inputs multiple times. |
92
|
|
|
|
93
|
|
|
Parameters |
94
|
|
|
---------- |
95
|
|
|
f : The function to be memoized. All arguments to f should be hashable. |
96
|
|
|
|
97
|
|
|
Example |
98
|
|
|
------- |
99
|
|
|
>>> counter = 0 |
100
|
|
|
>>> @remember_last |
101
|
|
|
... def foo(x): |
102
|
|
|
... global counter |
103
|
|
|
... counter += 1 |
104
|
|
|
... return x, counter |
105
|
|
|
>>> foo(1) |
106
|
|
|
(1, 1) |
107
|
|
|
>>> foo(1) |
108
|
|
|
(1, 1) |
109
|
|
|
>>> foo(0) |
110
|
|
|
(0, 2) |
111
|
|
|
>>> foo(1) |
112
|
|
|
(1, 3) |
113
|
|
|
|
114
|
|
|
Notes |
115
|
|
|
----- |
116
|
|
|
This decorator is equivalent to `lru_cache(1)` in Python 3, but with less |
117
|
|
|
bells and whistles for handling things like threadsafety. If we ever |
118
|
|
|
decide we need such bells and whistles, we should just make functools32 a |
119
|
|
|
dependency. |
120
|
|
|
""" |
121
|
|
|
# This needs to be a mutable data structure so we can change it from inside |
122
|
|
|
# the function. In pure Python 3, we'd use the nonlocal keyword for this. |
123
|
|
|
_previous = [None, None] |
124
|
|
|
KEY, VALUE = 0, 1 |
125
|
|
|
|
126
|
|
|
_kwd_mark = object() |
127
|
|
|
|
128
|
|
|
@wraps(f) |
129
|
|
|
def memoized_f(*args, **kwds): |
130
|
|
|
# Hashing logic taken from functools32.lru_cache. |
131
|
|
|
key = args |
132
|
|
|
if kwds: |
133
|
|
|
key += _kwd_mark + tuple(sorted(iteritems(kwds))) |
134
|
|
|
|
135
|
|
|
key_hash = hash(key) |
136
|
|
|
if key_hash != _previous[KEY]: |
137
|
|
|
_previous[VALUE] = f(*args, **kwds) |
138
|
|
|
_previous[KEY] = key_hash |
139
|
|
|
return _previous[VALUE] |
140
|
|
|
|
141
|
|
|
return memoized_f |
142
|
|
|
|