1 2"""Decorators and helper functions for writing well behaved decorators.""" 3 4from inspect import getargspec, formatargspec 5from threading import RLock 6 7from application.python.weakref import weakobjectmap 8 9 10__all__ = 'decorator', 'preserve_signature', 'execute_once' 11 12 13def decorator(func): 14 """A syntactic marker with no other effect than improving readability.""" 15 return func 16 17 18def preserve_signature(func): 19 """Preserve the original function signature and attributes in decorator wrappers.""" 20 def fix_signature(wrapper): 21 exec_scope = {} 22 parameters = formatargspec(*getargspec(func), formatvalue=lambda value: '') 23 exec 'def {0}{1}: return wrapper{1}'.format(func.__name__, parameters) in {'wrapper': wrapper}, exec_scope # can't use tuple form here (see https://bugs.python.org/issue21591) 24 new_wrapper = exec_scope.pop(func.__name__) 25 new_wrapper.__name__ = func.__name__ 26 new_wrapper.__doc__ = func.__doc__ 27 new_wrapper.__module__ = func.__module__ 28 new_wrapper.__defaults__ = func.__defaults__ 29 new_wrapper.__dict__.update(func.__dict__) 30 return new_wrapper 31 return fix_signature 32 33 34@decorator 35def execute_once(func): 36 """Execute function/method once per function/instance""" 37 38 # noinspection PyUnusedLocal 39 @preserve_signature(func) 40 def check_arguments(*args, **kw): 41 pass 42 43 class ExecuteOnceMethodWrapper(object): 44 __slots__ = '__weakref__', '__method__', 'im_func_wrapper', 'called', 'lock' 45 46 def __init__(self, method, func_wrapper): 47 self.__method__ = method 48 self.im_func_wrapper = func_wrapper 49 50 def __call__(self, *args, **kw): 51 with self.im_func_wrapper.lock: 52 method = self.__method__ 53 check_arguments.__get__(method.im_self, method.im_class)(*args, **kw) 54 instance = method.im_self if method.im_self is not None else args[0] 55 if self.im_func_wrapper.__callmap__.get(instance, False): 56 return 57 self.im_func_wrapper.__callmap__[instance] = True 58 self.im_func_wrapper.__callmap__[method.im_class] = True 59 return method.__call__(*args, **kw) 60 61 def __dir__(self): 62 return sorted(set(dir(self.__method__) + dir(self.__class__) + list(self.__slots__))) 63 64 def __get__(self, obj, cls): 65 method = self.__method__.__get__(obj, cls) 66 return self.__class__(method, self.im_func_wrapper) 67 68 def __getattr__(self, name): 69 return getattr(self.__method__, name) 70 71 def __setattr__(self, name, value): 72 if name in self.__slots__: 73 object.__setattr__(self, name, value) 74 else: 75 setattr(self.__method__, name, value) 76 77 def __delattr__(self, name): 78 if name in self.__slots__: 79 object.__delattr__(self, name) 80 else: 81 delattr(self.__method__, name) 82 83 def __repr__(self): 84 return self.__method__.__repr__().replace('<', '<wrapper of ', 1) 85 86 @property 87 def called(self): 88 return self.im_func_wrapper.__callmap__.get(self.__method__.im_self if self.__method__.im_self is not None else self.__method__.im_class, False) 89 90 @property 91 def lock(self): 92 return self.im_func_wrapper.lock 93 94 class ExecuteOnceFunctionWrapper(object): 95 __slots__ = '__weakref__', '__func__', '__callmap__', 'called', 'lock' 96 97 # noinspection PyShadowingNames 98 def __init__(self, func): 99 self.__func__ = func 100 self.__callmap__ = weakobjectmap() 101 self.__callmap__[func] = False 102 self.lock = RLock() 103 104 def __call__(self, *args, **kw): 105 with self.lock: 106 check_arguments(*args, **kw) 107 if self.__callmap__[self.__func__]: 108 return 109 self.__callmap__[self.__func__] = True 110 return self.__func__.__call__(*args, **kw) 111 112 def __dir__(self): 113 return sorted(set(dir(self.__func__) + dir(self.__class__) + list(self.__slots__))) 114 115 def __get__(self, obj, cls): 116 method = self.__func__.__get__(obj, cls) 117 return ExecuteOnceMethodWrapper(method, self) 118 119 def __getattr__(self, name): 120 return getattr(self.__func__, name) 121 122 def __setattr__(self, name, value): 123 if name in self.__slots__: 124 object.__setattr__(self, name, value) 125 else: 126 setattr(self.__func__, name, value) 127 128 def __delattr__(self, name): 129 if name in self.__slots__: 130 object.__delattr__(self, name) 131 else: 132 delattr(self.__func__, name) 133 134 def __repr__(self): 135 return self.__func__.__repr__().replace('<', '<wrapper of ', 1) 136 137 @property 138 def called(self): 139 return self.__callmap__[self.__func__] 140 141 return ExecuteOnceFunctionWrapper(func) 142 143 144__usage__ = """ 145from application.python.decorator import decorator, preserve_signature 146 147# indicate that the next function will be used as a decorator (optional) 148@decorator 149def print_args(func): 150 @preserve_signature(func) 151 def wrapper(*args, **kw): 152 print('arguments: args={}, kw={}'.format(args, kw)) 153 return func(*args, **kw) 154 return wrapper 155 156@print_args 157def foo(x, y, z=7): 158 return x + 3*y + z*z 159 160""" 161