decorators.py 8.9 KB

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