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

Source Code for Module logilab.common.decorators

  1  # copyright 2003-2011 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  __docformat__ = "restructuredtext en" 
 20   
 21  import sys 
 22  from time import clock, time 
 23   
 24  from logilab.common.compat import callable, method_type 
25 26 # XXX rewrite so we can use the decorator syntax when keyarg has to be specified 27 28 -def _is_generator_function(callableobj):
29 return callableobj.func_code.co_flags & 0x20
30
31 -class cached_decorator(object):
32 - def __init__(self, cacheattr=None, keyarg=None):
33 self.cacheattr = cacheattr 34 self.keyarg = keyarg
35 - def __call__(self, callableobj=None):
36 assert not _is_generator_function(callableobj), \ 37 'cannot cache generator function: %s' % callableobj 38 if callableobj.func_code.co_argcount == 1 or self.keyarg == 0: 39 cache = _SingleValueCache(callableobj, self.cacheattr) 40 elif self.keyarg: 41 cache = _MultiValuesKeyArgCache(callableobj, self.keyarg, self.cacheattr) 42 else: 43 cache = _MultiValuesCache(callableobj, self.cacheattr) 44 return cache.closure()
45
46 -class _SingleValueCache(object):
47 - def __init__(self, callableobj, cacheattr=None):
48 self.callable = callableobj 49 if cacheattr is None: 50 self.cacheattr = '_%s_cache_' % callableobj.__name__ 51 else: 52 assert cacheattr != callableobj.__name__ 53 self.cacheattr = cacheattr
54
55 - def __call__(__me, self, *args):
56 try: 57 return self.__dict__[__me.cacheattr] 58 except KeyError: 59 value = __me.callable(self, *args) 60 setattr(self, __me.cacheattr, value) 61 return value
62
63 - def closure(self):
64 def wrapped(*args, **kwargs): 65 return self.__call__(*args, **kwargs)
66 wrapped.cache_obj = self 67 try: 68 wrapped.__doc__ = self.callable.__doc__ 69 wrapped.__name__ = self.callable.__name__ 70 wrapped.func_name = self.callable.func_name 71 except: 72 pass 73 return wrapped
74
75 - def clear(self, holder):
76 holder.__dict__.pop(self.cacheattr, None)
77
78 79 -class _MultiValuesCache(_SingleValueCache):
80 - def _get_cache(self, holder):
81 try: 82 _cache = holder.__dict__[self.cacheattr] 83 except KeyError: 84 _cache = {} 85 setattr(holder, self.cacheattr, _cache) 86 return _cache
87
88 - def __call__(__me, self, *args, **kwargs):
89 _cache = __me._get_cache(self) 90 try: 91 return _cache[args] 92 except KeyError: 93 _cache[args] = __me.callable(self, *args) 94 return _cache[args]
95
96 -class _MultiValuesKeyArgCache(_MultiValuesCache):
97 - def __init__(self, callableobj, keyarg, cacheattr=None):
98 super(_MultiValuesKeyArgCache, self).__init__(callableobj, cacheattr) 99 self.keyarg = keyarg
100
101 - def __call__(__me, self, *args, **kwargs):
102 _cache = __me._get_cache(self) 103 key = args[__me.keyarg-1] 104 try: 105 return _cache[key] 106 except KeyError: 107 _cache[key] = __me.callable(self, *args, **kwargs) 108 return _cache[key]
109
110 111 -def cached(callableobj=None, keyarg=None, **kwargs):
112 """Simple decorator to cache result of method call.""" 113 kwargs['keyarg'] = keyarg 114 decorator = cached_decorator(**kwargs) 115 if callableobj is None: 116 return decorator 117 else: 118 return decorator(callableobj)
119
120 121 -class cachedproperty(object):
122 """ Provides a cached property equivalent to the stacking of 123 @cached and @property, but more efficient. 124 125 After first usage, the <property_name> becomes part of the object's 126 __dict__. Doing: 127 128 del obj.<property_name> empties the cache. 129 130 Idea taken from the pyramid_ framework and the mercurial_ project. 131 132 .. _pyramid: http://pypi.python.org/pypi/pyramid 133 .. _mercurial: http://pypi.python.org/pypi/Mercurial 134 """ 135 __slots__ = ('wrapped',) 136
137 - def __init__(self, wrapped):
138 try: 139 wrapped.__name__ 140 except AttributeError: 141 raise TypeError('%s must have a __name__ attribute' % 142 wrapped) 143 self.wrapped = wrapped
144 145 @property
146 - def __doc__(self):
147 doc = getattr(self.wrapped, '__doc__', None) 148 return ('<wrapped by the cachedproperty decorator>%s' 149 % ('\n%s' % doc if doc else ''))
150
151 - def __get__(self, inst, objtype=None):
152 if inst is None: 153 return self 154 val = self.wrapped(inst) 155 setattr(inst, self.wrapped.__name__, val) 156 return val
157
158 159 -def get_cache_impl(obj, funcname):
160 cls = obj.__class__ 161 member = getattr(cls, funcname) 162 if isinstance(member, property): 163 member = member.fget 164 return member.cache_obj
165
166 -def clear_cache(obj, funcname):
167 """Clear a cache handled by the :func:`cached` decorator. If 'x' class has 168 @cached on its method `foo`, type 169 170 >>> clear_cache(x, 'foo') 171 172 to purge this method's cache on the instance. 173 """ 174 get_cache_impl(obj, funcname).clear(obj)
175
176 -def copy_cache(obj, funcname, cacheobj):
177 """Copy cache for <funcname> from cacheobj to obj.""" 178 cacheattr = get_cache_impl(obj, funcname).cacheattr 179 try: 180 setattr(obj, cacheattr, cacheobj.__dict__[cacheattr]) 181 except KeyError: 182 pass
183
184 185 -class wproperty(object):
186 """Simple descriptor expecting to take a modifier function as first argument 187 and looking for a _<function name> to retrieve the attribute. 188 """
189 - def __init__(self, setfunc):
190 self.setfunc = setfunc 191 self.attrname = '_%s' % setfunc.__name__
192
193 - def __set__(self, obj, value):
194 self.setfunc(obj, value)
195
196 - def __get__(self, obj, cls):
197 assert obj is not None 198 return getattr(obj, self.attrname)
199
200 201 -class classproperty(object):
202 """this is a simple property-like class but for class attributes. 203 """
204 - def __init__(self, get):
205 self.get = get
206 - def __get__(self, inst, cls):
207 return self.get(cls)
208
209 210 -class iclassmethod(object):
211 '''Descriptor for method which should be available as class method if called 212 on the class or instance method if called on an instance. 213 '''
214 - def __init__(self, func):
215 self.func = func
216 - def __get__(self, instance, objtype):
217 if instance is None: 218 return method_type(self.func, objtype, objtype.__class__) 219 return method_type(self.func, instance, objtype)
220 - def __set__(self, instance, value):
221 raise AttributeError("can't set attribute")
222
223 224 -def timed(f):
225 def wrap(*args, **kwargs): 226 t = time() 227 c = clock() 228 res = f(*args, **kwargs) 229 print '%s clock: %.9f / time: %.9f' % (f.__name__, 230 clock() - c, time() - t) 231 return res
232 return wrap 233
234 235 -def locked(acquire, release):
236 """Decorator taking two methods to acquire/release a lock as argument, 237 returning a decorator function which will call the inner method after 238 having called acquire(self) et will call release(self) afterwards. 239 """ 240 def decorator(f): 241 def wrapper(self, *args, **kwargs): 242 acquire(self) 243 try: 244 return f(self, *args, **kwargs) 245 finally: 246 release(self)
247 return wrapper 248 return decorator 249
250 251 -def monkeypatch(klass, methodname=None):
252 """Decorator extending class with the decorated callable 253 >>> class A: 254 ... pass 255 >>> @monkeypatch(A) 256 ... def meth(self): 257 ... return 12 258 ... 259 >>> a = A() 260 >>> a.meth() 261 12 262 >>> @monkeypatch(A, 'foo') 263 ... def meth(self): 264 ... return 12 265 ... 266 >>> a.foo() 267 12 268 """ 269 def decorator(func): 270 try: 271 name = methodname or func.__name__ 272 except AttributeError: 273 raise AttributeError('%s has no __name__ attribute: ' 274 'you should provide an explicit `methodname`' 275 % 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) 282 return func
283 return decorator 284