1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
## The basic trick is to generate the source code for the decorated function
## with the right signature and to evaluate it.
## Uncomment the statement 'print >> sys.stderr, func_src' in _decorate
## to understand what is going on.
__all__ = ["decorator", "update_wrapper", "getinfo"]
import inspect, sys
def getinfo(func):
"""
Returns an info dictionary containing:
- name (the name of the function : str)
- argnames (the names of the arguments : list)
- defaults (the values of the default arguments : tuple)
- signature (the signature : str)
- doc (the docstring : str)
- module (the module name : str)
- dict (the function __dict__ : str)
>>> def f(self, x=1, y=2, *args, **kw): pass
>>> info = getinfo(f)
>>> info["name"]
'f'
>>> info["argnames"]
['self', 'x', 'y', 'args', 'kw']
>>> info["defaults"]
(1, 2)
>>> info["signature"]
'self, x, y, *args, **kw'
"""
assert inspect.ismethod(func) or inspect.isfunction(func)
regargs, varargs, varkwargs, defaults = inspect.getargspec(func)
argnames = list(regargs)
if varargs:
argnames.append(varargs)
if varkwargs:
argnames.append(varkwargs)
signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults,
formatvalue=lambda value: "")[1:-1]
return dict(name=func.__name__, argnames=argnames, signature=signature,
defaults = func.func_defaults, doc=func.__doc__,
module=func.__module__, dict=func.__dict__,
globals=func.func_globals, closure=func.func_closure)
def update_wrapper(wrapper, wrapped, create=False):
"""
An improvement over functools.update_wrapper. By default it works the
same, but if the 'create' flag is set, generates a copy of the wrapper
with the right signature and update the copy, not the original.
Moreovoer, 'wrapped' can be a dictionary with keys 'name', 'doc', 'module',
'dict', 'defaults'.
"""
if isinstance(wrapped, dict):
infodict = wrapped
else: # assume wrapped is a function
infodict = getinfo(wrapped)
assert not '_wrapper_' in infodict["argnames"], \
'"_wrapper_" is a reserved argument name!'
if create: # create a brand new wrapper with the right signature
src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict
# import sys; print >> sys.stderr, src # for debugging purposes
wrapper = eval(src, dict(_wrapper_=wrapper))
try:
wrapper.__name__ = infodict['name']
except: # Python version < 2.4
pass
wrapper.__doc__ = infodict['doc']
wrapper.__module__ = infodict['module']
wrapper.__dict__.update(infodict['dict'])
wrapper.func_defaults = infodict['defaults']
return wrapper
# the real meat is here
def _decorator(caller, func):
infodict = getinfo(func)
argnames = infodict['argnames']
assert not ('_call_' in argnames or '_func_' in argnames), \
'You cannot use _call_ or _func_ as argument names!'
src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict
dec_func = eval(src, dict(_func_=func, _call_=caller))
return update_wrapper(dec_func, func)
def decorator(caller, func=None):
"""
General purpose decorator factory: takes a caller function as
input and returns a decorator with the same attributes.
A caller function is any function like this::
def caller(func, *args, **kw):
# do something
return func(*args, **kw)
Here is an example of usage:
>>> @decorator
... def chatty(f, *args, **kw):
... print "Calling %r" % f.__name__
... return f(*args, **kw)
>>> chatty.__name__
'chatty'
>>> @chatty
... def f(): pass
...
>>> f()
Calling 'f'
For sake of convenience, the decorator factory can also be called with
two arguments. In this casem ``decorator(caller, func)`` is just a
shortcut for ``decorator(caller)(func)``.
"""
if func is None: # return a decorator function
return update_wrapper(lambda f : _decorator(caller, f), caller)
else: # return a decorated function
return _decorator(caller, func)
if __name__ == "__main__":
import doctest; doctest.testmod()
|