Package logilab :: Package common :: Module decorators
[frames] | no frames]

Source Code for Module logilab.common.decorators

  1  # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 
  2  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 
  3  # 
  4  # This file is part of logilab-common. 
  5  # 
  6  # logilab-common is free software: you can redistribute it and/or modify it under 
  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) any 
  9  # later version. 
 10  # 
 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 
 13  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 14  # details. 
 15  # 
 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/>. 
 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 
37 38 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified 39 40 -class cached_decorator(object):
41 - def __init__(self, cacheattr=None, keyarg=None):
42 self.cacheattr = cacheattr 43 self.keyarg = keyarg
44 - def __call__(self, callableobj=None):
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
55 -class _SingleValueCache(object):
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
64 - def __call__(__me, self, *args):
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
72 - def closure(self):
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
83 - def clear(self, holder):
84 holder.__dict__.pop(self.cacheattr, None)
85
86 87 -class _MultiValuesCache(_SingleValueCache):
88 - def _get_cache(self, holder):
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
104 -class _MultiValuesKeyArgCache(_MultiValuesCache):
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
128 129 -class cachedproperty(object):
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
145 - def __init__(self, wrapped):
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
154 - def __doc__(self):
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
166 167 -def get_cache_impl(obj, funcname):
168 cls = obj.__class__ 169 member = getattr(cls, funcname) 170 if isinstance(member, property): 171 member = member.fget 172 return member.cache_obj
173
174 -def clear_cache(obj, funcname):
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
184 -def copy_cache(obj, funcname, cacheobj):
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
192 193 -class wproperty(object):
194 """Simple descriptor expecting to take a modifier function as first argument 195 and looking for a _<function name> to retrieve the attribute. 196 """
197 - def __init__(self, setfunc):
198 self.setfunc = setfunc 199 self.attrname = '_%s' % setfunc.__name__
200
201 - def __set__(self, obj, value):
202 self.setfunc(obj, value)
203
204 - def __get__(self, obj, cls):
205 assert obj is not None 206 return getattr(obj, self.attrname)
207
208 209 -class classproperty(object):
210 """this is a simple property-like class but for class attributes. 211 """
212 - def __init__(self, get):
213 self.get = get
214 - def __get__(self, inst, cls):
215 return self.get(cls)
216
217 218 -class iclassmethod(object):
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 '''
222 - def __init__(self, func):
223 self.func = func
224 - def __get__(self, instance, objtype):
225 if instance is None: 226 return method_type(self.func, objtype, objtype.__class__) 227 return method_type(self.func, instance, objtype)
228 - def __set__(self, instance, value):
229 raise AttributeError("can't set attribute")
230
231 232 -def timed(f):
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
258 259 -def monkeypatch(klass, methodname=None):
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