OLD | NEW |
1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This file is part of logilab-common. | 4 # This file is part of logilab-common. |
5 # | 5 # |
6 # logilab-common is free software: you can redistribute it and/or modify it unde
r | 6 # logilab-common is free software: you can redistribute it and/or modify it unde
r |
7 # the terms of the GNU Lesser General Public License as published by the Free | 7 # the terms of the GNU Lesser General Public License as published by the Free |
8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y | 8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y |
9 # later version. | 9 # later version. |
10 # | 10 # |
11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT | 11 # logilab-common is distributed in the hope that it will be useful, but WITHOUT |
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | 13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
14 # details. | 14 # details. |
15 # | 15 # |
16 # You should have received a copy of the GNU Lesser General Public License along | 16 # You should have received a copy of the GNU Lesser General Public License along |
17 # with logilab-common. If not, see <http://www.gnu.org/licenses/>. | 17 # with logilab-common. If not, see <http://www.gnu.org/licenses/>. |
18 """ A few useful function/method decorators. """ | 18 """ A few useful function/method decorators. """ |
| 19 |
| 20 from __future__ import print_function |
| 21 |
19 __docformat__ = "restructuredtext en" | 22 __docformat__ = "restructuredtext en" |
20 | 23 |
21 import sys | 24 import sys |
| 25 import types |
22 from time import clock, time | 26 from time import clock, time |
| 27 from inspect import isgeneratorfunction, getargspec |
23 | 28 |
24 from logilab.common.compat import callable, method_type | 29 from logilab.common.compat import method_type |
25 | 30 |
26 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified | 31 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified |
27 | 32 |
28 def _is_generator_function(callableobj): | |
29 return callableobj.func_code.co_flags & 0x20 | |
30 | |
31 class cached_decorator(object): | 33 class cached_decorator(object): |
32 def __init__(self, cacheattr=None, keyarg=None): | 34 def __init__(self, cacheattr=None, keyarg=None): |
33 self.cacheattr = cacheattr | 35 self.cacheattr = cacheattr |
34 self.keyarg = keyarg | 36 self.keyarg = keyarg |
35 def __call__(self, callableobj=None): | 37 def __call__(self, callableobj=None): |
36 assert not _is_generator_function(callableobj), \ | 38 assert not isgeneratorfunction(callableobj), \ |
37 'cannot cache generator function: %s' % callableobj | 39 'cannot cache generator function: %s' % callableobj |
38 if callableobj.func_code.co_argcount == 1 or self.keyarg == 0: | 40 if len(getargspec(callableobj).args) == 1 or self.keyarg == 0: |
39 cache = _SingleValueCache(callableobj, self.cacheattr) | 41 cache = _SingleValueCache(callableobj, self.cacheattr) |
40 elif self.keyarg: | 42 elif self.keyarg: |
41 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cache
attr) | 43 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cache
attr) |
42 else: | 44 else: |
43 cache = _MultiValuesCache(callableobj, self.cacheattr) | 45 cache = _MultiValuesCache(callableobj, self.cacheattr) |
44 return cache.closure() | 46 return cache.closure() |
45 | 47 |
46 class _SingleValueCache(object): | 48 class _SingleValueCache(object): |
47 def __init__(self, callableobj, cacheattr=None): | 49 def __init__(self, callableobj, cacheattr=None): |
48 self.callable = callableobj | 50 self.callable = callableobj |
(...skipping 11 matching lines...) Expand all Loading... |
60 setattr(self, __me.cacheattr, value) | 62 setattr(self, __me.cacheattr, value) |
61 return value | 63 return value |
62 | 64 |
63 def closure(self): | 65 def closure(self): |
64 def wrapped(*args, **kwargs): | 66 def wrapped(*args, **kwargs): |
65 return self.__call__(*args, **kwargs) | 67 return self.__call__(*args, **kwargs) |
66 wrapped.cache_obj = self | 68 wrapped.cache_obj = self |
67 try: | 69 try: |
68 wrapped.__doc__ = self.callable.__doc__ | 70 wrapped.__doc__ = self.callable.__doc__ |
69 wrapped.__name__ = self.callable.__name__ | 71 wrapped.__name__ = self.callable.__name__ |
70 wrapped.func_name = self.callable.func_name | |
71 except: | 72 except: |
72 pass | 73 pass |
73 return wrapped | 74 return wrapped |
74 | 75 |
75 def clear(self, holder): | 76 def clear(self, holder): |
76 holder.__dict__.pop(self.cacheattr, None) | 77 holder.__dict__.pop(self.cacheattr, None) |
77 | 78 |
78 | 79 |
79 class _MultiValuesCache(_SingleValueCache): | 80 class _MultiValuesCache(_SingleValueCache): |
80 def _get_cache(self, holder): | 81 def _get_cache(self, holder): |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
219 return method_type(self.func, instance, objtype) | 220 return method_type(self.func, instance, objtype) |
220 def __set__(self, instance, value): | 221 def __set__(self, instance, value): |
221 raise AttributeError("can't set attribute") | 222 raise AttributeError("can't set attribute") |
222 | 223 |
223 | 224 |
224 def timed(f): | 225 def timed(f): |
225 def wrap(*args, **kwargs): | 226 def wrap(*args, **kwargs): |
226 t = time() | 227 t = time() |
227 c = clock() | 228 c = clock() |
228 res = f(*args, **kwargs) | 229 res = f(*args, **kwargs) |
229 print '%s clock: %.9f / time: %.9f' % (f.__name__, | 230 print('%s clock: %.9f / time: %.9f' % (f.__name__, |
230 clock() - c, time() - t) | 231 clock() - c, time() - t)) |
231 return res | 232 return res |
232 return wrap | 233 return wrap |
233 | 234 |
234 | 235 |
235 def locked(acquire, release): | 236 def locked(acquire, release): |
236 """Decorator taking two methods to acquire/release a lock as argument, | 237 """Decorator taking two methods to acquire/release a lock as argument, |
237 returning a decorator function which will call the inner method after | 238 returning a decorator function which will call the inner method after |
238 having called acquire(self) et will call release(self) afterwards. | 239 having called acquire(self) et will call release(self) afterwards. |
239 """ | 240 """ |
240 def decorator(f): | 241 def decorator(f): |
241 def wrapper(self, *args, **kwargs): | 242 def wrapper(self, *args, **kwargs): |
242 acquire(self) | 243 acquire(self) |
243 try: | 244 try: |
244 return f(self, *args, **kwargs) | 245 return f(self, *args, **kwargs) |
245 finally: | 246 finally: |
246 release(self) | 247 release(self) |
247 return wrapper | 248 return wrapper |
248 return decorator | 249 return decorator |
249 | 250 |
250 | 251 |
251 def monkeypatch(klass, methodname=None): | 252 def monkeypatch(klass, methodname=None): |
252 """Decorator extending class with the decorated callable | 253 """Decorator extending class with the decorated callable. This is basically |
| 254 a syntactic sugar vs class assignment. |
| 255 |
253 >>> class A: | 256 >>> class A: |
254 ... pass | 257 ... pass |
255 >>> @monkeypatch(A) | 258 >>> @monkeypatch(A) |
256 ... def meth(self): | 259 ... def meth(self): |
257 ... return 12 | 260 ... return 12 |
258 ... | 261 ... |
259 >>> a = A() | 262 >>> a = A() |
260 >>> a.meth() | 263 >>> a.meth() |
261 12 | 264 12 |
262 >>> @monkeypatch(A, 'foo') | 265 >>> @monkeypatch(A, 'foo') |
263 ... def meth(self): | 266 ... def meth(self): |
264 ... return 12 | 267 ... return 12 |
265 ... | 268 ... |
266 >>> a.foo() | 269 >>> a.foo() |
267 12 | 270 12 |
268 """ | 271 """ |
269 def decorator(func): | 272 def decorator(func): |
270 try: | 273 try: |
271 name = methodname or func.__name__ | 274 name = methodname or func.__name__ |
272 except AttributeError: | 275 except AttributeError: |
273 raise AttributeError('%s has no __name__ attribute: ' | 276 raise AttributeError('%s has no __name__ attribute: ' |
274 'you should provide an explicit `methodname`' | 277 'you should provide an explicit `methodname`' |
275 % func) | 278 % func) |
276 if callable(func) and sys.version_info < (3, 0): | 279 setattr(klass, name, func) |
277 setattr(klass, name, method_type(func, None, klass)) | |
278 else: | |
279 # likely a property | |
280 # this is quite borderline but usage already in the wild ... | |
281 setattr(klass, name, func) | |
282 return func | 280 return func |
283 return decorator | 281 return decorator |
OLD | NEW |