"""A few useful function/method decorators. :copyright: 2006-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: General Public License version 2 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" from types import MethodType from time import clock import sys, re # XXX rewrite so we can use the decorator syntax when keyarg has to be specified def cached(callableobj, keyarg=None): """Simple decorator to cache result of method call.""" if callableobj.__code__.co_argcount == 1 or keyarg == 0: def cache_wrapper1(self, *args): cache = '_%s_cache_' % callableobj.__name__ try: return self.__dict__[cache] except KeyError: value = callableobj(self, *args) setattr(self, cache, value) return value return cache_wrapper1 elif keyarg: def cache_wrapper2(self, *args, **kwargs): cache = '_%s_cache_' % callableobj.__name__ key = args[keyarg-1] try: _cache = self.__dict__[cache] except KeyError: _cache = {} setattr(self, cache, _cache) try: return _cache[key] except KeyError: _cache[key] = callableobj(self, *args, **kwargs) return _cache[key] return cache_wrapper2 def cache_wrapper3(self, *args): cache = '_%s_cache_' % callableobj.__name__ try: _cache = self.__dict__[cache] except KeyError: _cache = {} setattr(self, cache, _cache) try: return _cache[args] except KeyError: _cache[args] = callableobj(self, *args) return _cache[args] return cache_wrapper3 def clear_cache(obj, funcname): """Function to clear a cache handled by the cached decorator.""" try: del obj.__dict__['_%s_cache_' % funcname] except KeyError: pass def copy_cache(obj, funcname, cacheobj): """Copy cache for from cacheobj to obj.""" cache = '_%s_cache_' % funcname try: setattr(obj, cache, cacheobj.__dict__[cache]) except KeyError: pass class wproperty(object): """Simple descriptor expecting to take a modifier function as first argument and looking for a _ to retrieve the attribute. """ def __init__(self, setfunc): self.setfunc = setfunc self.attrname = '_%s' % setfunc.__name__ def __set__(self, obj, value): self.setfunc(obj, value) def __get__(self, obj, cls): assert obj is not None return getattr(obj, self.attrname) class classproperty(object): def __init__(self, get): self.get = get def __get__(self, inst, cls): return self.get(cls) class iclassmethod(object): '''Descriptor for method which should be available as class method if called on the class or instance method if called on an instance. ''' def __init__(self, func): self.func = func def __get__(self, instance, objtype): if instance is None: return MethodType(self.func, objtype, objtype.__class__) return MethodType(self.func, instance, objtype) def __set__(self, instance, value): raise AttributeError("can't set attribute") def timed(f): def wrap(*args, **kwargs): t = clock() #for i in range(100): res = f(*args, **kwargs) print('%s time: %.9f' % (f.__name__, clock() - t)) return res return wrap def locked(acquire, release): """Decorator taking two methods to acquire/release a lock as argument, returning a decorator function which will call the inner method after having called acquire(self) et will call release(self) afterwards. """ def decorator(f): def wrapper(self, *args, **kwargs): acquire(self) try: return f(self, *args, **kwargs) finally: release(self) return wrapper return decorator def monkeypatch(klass, methodname=None): """Decorator extending class with the decorated function >>> class A: ... pass >>> @monkeypatch(A) ... def meth(self): ... return 12 ... >>> a = A() >>> a.meth() 12 >>> @monkeypatch(A, 'foo') ... def meth(self): ... return 12 ... >>> a.foo() 12 """ def decorator(func): setattr(klass, methodname or func.__name__, func) return func return decorator