1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """ A few useful function/method decorators. """
19
20 from __future__ import print_function
21
22 __docformat__ = "restructuredtext en"
23
24 import sys
25 import types
26 from time import clock, time
27 from inspect import isgeneratorfunction
28
29 import six
30
31 if six.PY3:
32 from inspect import getfullargspec
33 else:
34 from inspect import getargspec as getfullargspec
35
36 from logilab.common.compat import method_type
41 - def __init__(self, cacheattr=None, keyarg=None):
42 self.cacheattr = cacheattr
43 self.keyarg = keyarg
45 assert not isgeneratorfunction(callableobj), \
46 'cannot cache generator function: %s' % callableobj
47 if len(getfullargspec(callableobj).args) == 1 or self.keyarg == 0:
48 cache = _SingleValueCache(callableobj, self.cacheattr)
49 elif self.keyarg:
50 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cacheattr)
51 else:
52 cache = _MultiValuesCache(callableobj, self.cacheattr)
53 return cache.closure()
54
56 - def __init__(self, callableobj, cacheattr=None):
57 self.callable = callableobj
58 if cacheattr is None:
59 self.cacheattr = '_%s_cache_' % callableobj.__name__
60 else:
61 assert cacheattr != callableobj.__name__
62 self.cacheattr = cacheattr
63
65 try:
66 return self.__dict__[__me.cacheattr]
67 except KeyError:
68 value = __me.callable(self, *args)
69 setattr(self, __me.cacheattr, value)
70 return value
71
73 def wrapped(*args, **kwargs):
74 return self.__call__(*args, **kwargs)
75 wrapped.cache_obj = self
76 try:
77 wrapped.__doc__ = self.callable.__doc__
78 wrapped.__name__ = self.callable.__name__
79 except:
80 pass
81 return wrapped
82
84 holder.__dict__.pop(self.cacheattr, None)
85
89 try:
90 _cache = holder.__dict__[self.cacheattr]
91 except KeyError:
92 _cache = {}
93 setattr(holder, self.cacheattr, _cache)
94 return _cache
95
96 - def __call__(__me, self, *args, **kwargs):
97 _cache = __me._get_cache(self)
98 try:
99 return _cache[args]
100 except KeyError:
101 _cache[args] = __me.callable(self, *args)
102 return _cache[args]
103
105 - def __init__(self, callableobj, keyarg, cacheattr=None):
106 super(_MultiValuesKeyArgCache, self).__init__(callableobj, cacheattr)
107 self.keyarg = keyarg
108
109 - def __call__(__me, self, *args, **kwargs):
110 _cache = __me._get_cache(self)
111 key = args[__me.keyarg-1]
112 try:
113 return _cache[key]
114 except KeyError:
115 _cache[key] = __me.callable(self, *args, **kwargs)
116 return _cache[key]
117
118
119 -def cached(callableobj=None, keyarg=None, **kwargs):
120 """Simple decorator to cache result of method call."""
121 kwargs['keyarg'] = keyarg
122 decorator = cached_decorator(**kwargs)
123 if callableobj is None:
124 return decorator
125 else:
126 return decorator(callableobj)
127
130 """ Provides a cached property equivalent to the stacking of
131 @cached and @property, but more efficient.
132
133 After first usage, the <property_name> becomes part of the object's
134 __dict__. Doing:
135
136 del obj.<property_name> empties the cache.
137
138 Idea taken from the pyramid_ framework and the mercurial_ project.
139
140 .. _pyramid: http://pypi.python.org/pypi/pyramid
141 .. _mercurial: http://pypi.python.org/pypi/Mercurial
142 """
143 __slots__ = ('wrapped',)
144
146 try:
147 wrapped.__name__
148 except AttributeError:
149 raise TypeError('%s must have a __name__ attribute' %
150 wrapped)
151 self.wrapped = wrapped
152
153 @property
155 doc = getattr(self.wrapped, '__doc__', None)
156 return ('<wrapped by the cachedproperty decorator>%s'
157 % ('\n%s' % doc if doc else ''))
158
159 - def __get__(self, inst, objtype=None):
160 if inst is None:
161 return self
162 val = self.wrapped(inst)
163 setattr(inst, self.wrapped.__name__, val)
164 return val
165
168 cls = obj.__class__
169 member = getattr(cls, funcname)
170 if isinstance(member, property):
171 member = member.fget
172 return member.cache_obj
173
175 """Clear a cache handled by the :func:`cached` decorator. If 'x' class has
176 @cached on its method `foo`, type
177
178 >>> clear_cache(x, 'foo')
179
180 to purge this method's cache on the instance.
181 """
182 get_cache_impl(obj, funcname).clear(obj)
183
185 """Copy cache for <funcname> from cacheobj to obj."""
186 cacheattr = get_cache_impl(obj, funcname).cacheattr
187 try:
188 setattr(obj, cacheattr, cacheobj.__dict__[cacheattr])
189 except KeyError:
190 pass
191
194 """Simple descriptor expecting to take a modifier function as first argument
195 and looking for a _<function name> to retrieve the attribute.
196 """
198 self.setfunc = setfunc
199 self.attrname = '_%s' % setfunc.__name__
200
202 self.setfunc(obj, value)
203
205 assert obj is not None
206 return getattr(obj, self.attrname)
207
210 """this is a simple property-like class but for class attributes.
211 """
216
219 '''Descriptor for method which should be available as class method if called
220 on the class or instance method if called on an instance.
221 '''
224 - def __get__(self, instance, objtype):
228 - def __set__(self, instance, value):
229 raise AttributeError("can't set attribute")
230
233 def wrap(*args, **kwargs):
234 t = time()
235 c = clock()
236 res = f(*args, **kwargs)
237 print('%s clock: %.9f / time: %.9f' % (f.__name__,
238 clock() - c, time() - t))
239 return res
240 return wrap
241
242
243 -def locked(acquire, release):
244 """Decorator taking two methods to acquire/release a lock as argument,
245 returning a decorator function which will call the inner method after
246 having called acquire(self) et will call release(self) afterwards.
247 """
248 def decorator(f):
249 def wrapper(self, *args, **kwargs):
250 acquire(self)
251 try:
252 return f(self, *args, **kwargs)
253 finally:
254 release(self)
255 return wrapper
256 return decorator
257
260 """Decorator extending class with the decorated callable. This is basically
261 a syntactic sugar vs class assignment.
262
263 >>> class A:
264 ... pass
265 >>> @monkeypatch(A)
266 ... def meth(self):
267 ... return 12
268 ...
269 >>> a = A()
270 >>> a.meth()
271 12
272 >>> @monkeypatch(A, 'foo')
273 ... def meth(self):
274 ... return 12
275 ...
276 >>> a.foo()
277 12
278 """
279 def decorator(func):
280 try:
281 name = methodname or func.__name__
282 except AttributeError:
283 raise AttributeError('%s has no __name__ attribute: '
284 'you should provide an explicit `methodname`'
285 % func)
286 setattr(klass, name, func)
287 return func
288 return decorator
289