Completed
Pull Request — master (#940)
by Joe
01:26
created

zipline.utils.classlazyval.__get__()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 2
rs 10
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