| OLD | NEW |
| 1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 1 # copyright 2003-2011 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 | |
| 22 __docformat__ = "restructuredtext en" | 19 __docformat__ = "restructuredtext en" |
| 23 | 20 |
| 24 import sys | 21 import sys |
| 25 import types | |
| 26 from time import clock, time | 22 from time import clock, time |
| 27 from inspect import isgeneratorfunction, getargspec | |
| 28 | 23 |
| 29 from logilab.common.compat import method_type | 24 from logilab.common.compat import callable, method_type |
| 30 | 25 |
| 31 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified | 26 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified |
| 32 | 27 |
| 28 def _is_generator_function(callableobj): |
| 29 return callableobj.func_code.co_flags & 0x20 |
| 30 |
| 33 class cached_decorator(object): | 31 class cached_decorator(object): |
| 34 def __init__(self, cacheattr=None, keyarg=None): | 32 def __init__(self, cacheattr=None, keyarg=None): |
| 35 self.cacheattr = cacheattr | 33 self.cacheattr = cacheattr |
| 36 self.keyarg = keyarg | 34 self.keyarg = keyarg |
| 37 def __call__(self, callableobj=None): | 35 def __call__(self, callableobj=None): |
| 38 assert not isgeneratorfunction(callableobj), \ | 36 assert not _is_generator_function(callableobj), \ |
| 39 'cannot cache generator function: %s' % callableobj | 37 'cannot cache generator function: %s' % callableobj |
| 40 if len(getargspec(callableobj).args) == 1 or self.keyarg == 0: | 38 if callableobj.func_code.co_argcount == 1 or self.keyarg == 0: |
| 41 cache = _SingleValueCache(callableobj, self.cacheattr) | 39 cache = _SingleValueCache(callableobj, self.cacheattr) |
| 42 elif self.keyarg: | 40 elif self.keyarg: |
| 43 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cache
attr) | 41 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cache
attr) |
| 44 else: | 42 else: |
| 45 cache = _MultiValuesCache(callableobj, self.cacheattr) | 43 cache = _MultiValuesCache(callableobj, self.cacheattr) |
| 46 return cache.closure() | 44 return cache.closure() |
| 47 | 45 |
| 48 class _SingleValueCache(object): | 46 class _SingleValueCache(object): |
| 49 def __init__(self, callableobj, cacheattr=None): | 47 def __init__(self, callableobj, cacheattr=None): |
| 50 self.callable = callableobj | 48 self.callable = callableobj |
| (...skipping 11 matching lines...) Expand all Loading... |
| 62 setattr(self, __me.cacheattr, value) | 60 setattr(self, __me.cacheattr, value) |
| 63 return value | 61 return value |
| 64 | 62 |
| 65 def closure(self): | 63 def closure(self): |
| 66 def wrapped(*args, **kwargs): | 64 def wrapped(*args, **kwargs): |
| 67 return self.__call__(*args, **kwargs) | 65 return self.__call__(*args, **kwargs) |
| 68 wrapped.cache_obj = self | 66 wrapped.cache_obj = self |
| 69 try: | 67 try: |
| 70 wrapped.__doc__ = self.callable.__doc__ | 68 wrapped.__doc__ = self.callable.__doc__ |
| 71 wrapped.__name__ = self.callable.__name__ | 69 wrapped.__name__ = self.callable.__name__ |
| 70 wrapped.func_name = self.callable.func_name |
| 72 except: | 71 except: |
| 73 pass | 72 pass |
| 74 return wrapped | 73 return wrapped |
| 75 | 74 |
| 76 def clear(self, holder): | 75 def clear(self, holder): |
| 77 holder.__dict__.pop(self.cacheattr, None) | 76 holder.__dict__.pop(self.cacheattr, None) |
| 78 | 77 |
| 79 | 78 |
| 80 class _MultiValuesCache(_SingleValueCache): | 79 class _MultiValuesCache(_SingleValueCache): |
| 81 def _get_cache(self, holder): | 80 def _get_cache(self, holder): |
| (...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 220 return method_type(self.func, instance, objtype) | 219 return method_type(self.func, instance, objtype) |
| 221 def __set__(self, instance, value): | 220 def __set__(self, instance, value): |
| 222 raise AttributeError("can't set attribute") | 221 raise AttributeError("can't set attribute") |
| 223 | 222 |
| 224 | 223 |
| 225 def timed(f): | 224 def timed(f): |
| 226 def wrap(*args, **kwargs): | 225 def wrap(*args, **kwargs): |
| 227 t = time() | 226 t = time() |
| 228 c = clock() | 227 c = clock() |
| 229 res = f(*args, **kwargs) | 228 res = f(*args, **kwargs) |
| 230 print('%s clock: %.9f / time: %.9f' % (f.__name__, | 229 print '%s clock: %.9f / time: %.9f' % (f.__name__, |
| 231 clock() - c, time() - t)) | 230 clock() - c, time() - t) |
| 232 return res | 231 return res |
| 233 return wrap | 232 return wrap |
| 234 | 233 |
| 235 | 234 |
| 236 def locked(acquire, release): | 235 def locked(acquire, release): |
| 237 """Decorator taking two methods to acquire/release a lock as argument, | 236 """Decorator taking two methods to acquire/release a lock as argument, |
| 238 returning a decorator function which will call the inner method after | 237 returning a decorator function which will call the inner method after |
| 239 having called acquire(self) et will call release(self) afterwards. | 238 having called acquire(self) et will call release(self) afterwards. |
| 240 """ | 239 """ |
| 241 def decorator(f): | 240 def decorator(f): |
| 242 def wrapper(self, *args, **kwargs): | 241 def wrapper(self, *args, **kwargs): |
| 243 acquire(self) | 242 acquire(self) |
| 244 try: | 243 try: |
| 245 return f(self, *args, **kwargs) | 244 return f(self, *args, **kwargs) |
| 246 finally: | 245 finally: |
| 247 release(self) | 246 release(self) |
| 248 return wrapper | 247 return wrapper |
| 249 return decorator | 248 return decorator |
| 250 | 249 |
| 251 | 250 |
| 252 def monkeypatch(klass, methodname=None): | 251 def monkeypatch(klass, methodname=None): |
| 253 """Decorator extending class with the decorated callable. This is basically | 252 """Decorator extending class with the decorated callable |
| 254 a syntactic sugar vs class assignment. | |
| 255 | |
| 256 >>> class A: | 253 >>> class A: |
| 257 ... pass | 254 ... pass |
| 258 >>> @monkeypatch(A) | 255 >>> @monkeypatch(A) |
| 259 ... def meth(self): | 256 ... def meth(self): |
| 260 ... return 12 | 257 ... return 12 |
| 261 ... | 258 ... |
| 262 >>> a = A() | 259 >>> a = A() |
| 263 >>> a.meth() | 260 >>> a.meth() |
| 264 12 | 261 12 |
| 265 >>> @monkeypatch(A, 'foo') | 262 >>> @monkeypatch(A, 'foo') |
| 266 ... def meth(self): | 263 ... def meth(self): |
| 267 ... return 12 | 264 ... return 12 |
| 268 ... | 265 ... |
| 269 >>> a.foo() | 266 >>> a.foo() |
| 270 12 | 267 12 |
| 271 """ | 268 """ |
| 272 def decorator(func): | 269 def decorator(func): |
| 273 try: | 270 try: |
| 274 name = methodname or func.__name__ | 271 name = methodname or func.__name__ |
| 275 except AttributeError: | 272 except AttributeError: |
| 276 raise AttributeError('%s has no __name__ attribute: ' | 273 raise AttributeError('%s has no __name__ attribute: ' |
| 277 'you should provide an explicit `methodname`' | 274 'you should provide an explicit `methodname`' |
| 278 % func) | 275 % func) |
| 279 setattr(klass, name, func) | 276 if callable(func) and sys.version_info < (3, 0): |
| 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) |
| 280 return func | 282 return func |
| 281 return decorator | 283 return decorator |
| OLD | NEW |