Completed
Push — develop ( b15263...cccc5b )
by Kale
58s
created

auxlib.class_property   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 28
Duplicated Lines 0 %
Metric Value
dl 0
loc 28
rs 10
wmc 11

4 Methods

Rating   Name   Duplication   Size   Complexity  
A auxlib.classproperty.setter() 0 2 1
A auxlib.classproperty.__get__() 0 4 3
A auxlib.classproperty.__set__() 0 4 2
B auxlib.classproperty.__init__() 0 12 5
1
from __future__ import absolute_import, division, print_function
2
import types
3
4
from ._vendor.six import wraps
5
6
# TODO: spend time filling out functionality and make these more robust
7
8
9
def memoize(func):
10
    """
11
    Decorator to cause a function to cache it's results for each combination of
12
    inputs and return the cached result on subsequent calls.  Does not support
13
    named arguments or arg values that are not hashable.
14
15
    >>> @memoize
16
    ... def foo(x):
17
    ...     print('running function with', x)
18
    ...     return x+3
19
    ...
20
    >>> foo(10)
21
    running function with 10
22
    13
23
    >>> foo(10)
24
    13
25
    >>> foo(11)
26
    running function with 11
27
    14
28
    >>> @memoize
29
    ... def range_tuple(limit):
30
    ...     print('running function')
31
    ...     return tuple(i for i in range(limit))
32
    ...
33
    >>> range_tuple(3)
34
    running function
35
    (0, 1, 2)
36
    >>> range_tuple(3)
37
    (0, 1, 2)
38
    >>> @memoize
39
    ... def range_iter(limit):
40
    ...     print('running function')
41
    ...     return (i for i in range(limit))
42
    ...
43
    >>> range_iter(3)
44
    Traceback (most recent call last):
45
    TypeError: Can't memoize a generator!
46
    """
47
    func._result_cache = {}  # pylint: disable-msg=W0212
48
49
    @wraps(func)
50
    def _memoized_func(*args, **kwargs):
51
        key = (args, tuple(sorted(kwargs.items())))
52
        if key in func._result_cache:  # pylint: disable-msg=W0212
53
            return func._result_cache[key]  # pylint: disable-msg=W0212
54
        else:
55
            result = func(*args, **kwargs)
56
            if isinstance(result, types.GeneratorType):
57
                raise TypeError("Can't memoize a generator!")
58
            func._result_cache[key] = result  # pylint: disable-msg=W0212
59
            return result
60
61
    return _memoized_func
62
63
64
def memoizemethod(method):
65
    """
66
    Decorator to cause a method to cache it's results in self for each
67
    combination of inputs and return the cached result on subsequent calls.
68
    Does not support named arguments or arg values that are not hashable.
69
70
    >>> class Foo (object):
71
    ...   @memoizemethod
72
    ...   def foo(self, x, y=0):
73
    ...     print('running method with', x, y)
74
    ...     return x + y + 3
75
    ...
76
    >>> foo1 = Foo()
77
    >>> foo2 = Foo()
78
    >>> foo1.foo(10)
79
    running method with 10 0
80
    13
81
    >>> foo1.foo(10)
82
    13
83
    >>> foo2.foo(11, y=7)
84
    running method with 11 7
85
    21
86
    >>> foo2.foo(11)
87
    running method with 11 0
88
    14
89
    >>> foo2.foo(11, y=7)
90
    21
91
    >>> class Foo (object):
92
    ...   def __init__(self, lower):
93
    ...     self.lower = lower
94
    ...   @memoizemethod
95
    ...   def range_tuple(self, upper):
96
    ...     print('running function')
97
    ...     return tuple(i for i in range(self.lower, upper))
98
    ...   @memoizemethod
99
    ...   def range_iter(self, upper):
100
    ...     print('running function')
101
    ...     return (i for i in range(self.lower, upper))
102
    ...
103
    >>> foo = Foo(3)
104
    >>> foo.range_tuple(6)
105
    running function
106
    (3, 4, 5)
107
    >>> foo.range_tuple(7)
108
    running function
109
    (3, 4, 5, 6)
110
    >>> foo.range_tuple(6)
111
    (3, 4, 5)
112
    >>> foo.range_iter(6)
113
    Traceback (most recent call last):
114
    TypeError: Can't memoize a generator!
115
    """
116
117
    @wraps(method)
118
    def _wrapper(self, *args, **kwargs):
119
        # NOTE:  a __dict__ check is performed here rather than using the
120
        # built-in hasattr function because hasattr will look up to an object's
121
        # class if the attr is not directly found in the object's dict.  That's
122
        # bad for this if the class itself has a memoized classmethod for
123
        # example that has been called before the memoized instance method,
124
        # then the instance method will use the class's result cache, causing
125
        # its results to be globally stored rather than on a per instance
126
        # basis.
127
        if '_memoized_results' not in self.__dict__:
128
            self._memoized_results = {}
129
        memoized_results = self._memoized_results
130
131
        key = (method.__name__, args, tuple(sorted(kwargs.items())))
132
        if key in memoized_results:
133
            return memoized_results[key]
134
        else:
135
            try:
136
                result = method(self, *args, **kwargs)
137
            except KeyError as e:
138
                if '__wrapped__' in str(e):
139
                    result = None  # is this the right thing to do?  happened during py3 conversion
140
                else:
141
                    raise
142
            if isinstance(result, types.GeneratorType):
143
                raise TypeError("Can't memoize a generator!")
144
            return memoized_results.setdefault(key, result)
145
146
    return _wrapper
147
148
149
# class memoizemethod(object):
150
#     """cache the return value of a method
151
#
152
#     This class is meant to be used as a decorator of methods. The return value
153
#     from a given method invocation will be cached on the instance whose method
154
#     was invoked. All arguments passed to a method decorated with memoize must
155
#     be hashable.
156
#
157
#     If a memoized method is invoked directly on its class the result will not
158
#     be cached. Instead the method will be invoked like a static method:
159
#     class Obj(object):
160
#         @memoize
161
#         def add_to(self, arg):
162
#             return self + arg
163
#     Obj.add_to(1) # not enough arguments
164
#     Obj.add_to(1, 2) # returns 3, result is not cached
165
#     """
166
#     def __init__(self, func):
167
#         self.func = func
168
#     def __get__(self, obj, objtype=None):
169
#         if obj is None:
170
#             return self.func
171
#         return partial(self, obj)
172
#     def __call__(self, *args, **kw):
173
#         obj = args[0]
174
#         try:
175
#             cache = obj.__cache
176
#         except AttributeError:
177
#             cache = obj.__cache = {}
178
#         key = (self.func, args[1:], frozenset(kw.items()))
179
#         try:
180
#             res = cache[key]
181
#         except KeyError:
182
#             res = cache[key] = self.func(*args, **kw)
183
#         return res
184
185
186
def clear_memoized_methods(obj, *method_names):
187
    """
188
    Clear the memoized method or @memoizeproperty results for the given
189
    method names from the given object.
190
191
    >>> v = [0]
192
    >>> def inc():
193
    ...     v[0] += 1
194
    ...     return v[0]
195
    ...
196
    >>> class Foo(object):
197
    ...    @memoizemethod
198
    ...    def foo(self):
199
    ...        return inc()
200
    ...    @memoizeproperty
201
    ...    def g(self):
202
    ...       return inc()
203
    ...
204
    >>> f = Foo()
205
    >>> f.foo(), f.foo()
206
    (1, 1)
207
    >>> clear_memoized_methods(f, 'foo')
208
    >>> (f.foo(), f.foo(), f.g, f.g)
209
    (2, 2, 3, 3)
210
    >>> (f.foo(), f.foo(), f.g, f.g)
211
    (2, 2, 3, 3)
212
    >>> clear_memoized_methods(f, 'g', 'no_problem_if_undefined')
213
    >>> f.g, f.foo(), f.g
214
    (4, 2, 4)
215
    >>> f.foo()
216
    2
217
    """
218
    for key in list(getattr(obj, '_memoized_results', {}).keys()):
219
        # key[0] is the method name
220
        if key[0] in method_names:
221
            del obj._memoized_results[key]
222
223
    property_dict = obj.__dict__
224
    for prop in method_names:
225
        inner_attname = '_{0}'.format(prop)
226
        if inner_attname in property_dict:
227
            del property_dict[inner_attname]
228
229
230
def memoizeproperty(func):
231
    """
232
    Decorator to cause a method to cache it's results in self for each
233
    combination of inputs and return the cached result on subsequent calls.
234
    Does not support named arguments or arg values that are not hashable.
235
236
    >>> class Foo (object):
237
    ...   _x = 1
238
    ...   @memoizeproperty
239
    ...   def foo(self):
240
    ...     self._x += 1
241
    ...     print('updating and returning {0}'.format(self._x))
242
    ...     return self._x
243
    ...
244
    >>> foo1 = Foo()
245
    >>> foo2 = Foo()
246
    >>> foo1.foo
247
    updating and returning 2
248
    2
249
    >>> foo1.foo
250
    2
251
    >>> foo2.foo
252
    updating and returning 2
253
    2
254
    >>> foo1.foo
255
    2
256
    """
257
    inner_attname = '_{0}'.format(func.__name__)
258
259
    def new_fget(self):
260
        self_dict = self.__dict__
261
        if inner_attname not in self_dict:
262
            self_dict[inner_attname] = func(self)
263
        return self_dict[inner_attname]
264
265
    return property(new_fget)
266
267
268
class classproperty(object):  # pylint: disable=C0103
269
    # from celery.five
270
271
    def __init__(self, getter=None, setter=None):
272
        if getter is not None and not isinstance(getter, classmethod):
273
            getter = classmethod(getter)
274
        if setter is not None and not isinstance(setter, classmethod):
275
            setter = classmethod(setter)
276
        self.__get = getter
277
        self.__set = setter
278
279
        info = getter.__get__(object)  # just need the info attrs.
280
        self.__doc__ = info.__doc__
281
        self.__name__ = info.__name__
282
        self.__module__ = info.__module__
283
284
    def __get__(self, obj, type_=None):
285
        if obj and type_ is None:
286
            type_ = obj.__class__
287
        return self.__get.__get__(obj, type_)()
288
289
    def __set__(self, obj, value):
290
        if obj is None:
291
            return self
292
        return self.__set.__get__(obj)(value)
293
294
    def setter(self, setter):
295
        return self.__class__(self.__get, setter)
296