OLD | NEW |
(Empty) | |
| 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 unde
r |
| 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 |
| 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.cache
attr) |
| 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 |
OLD | NEW |