-
Notifications
You must be signed in to change notification settings - Fork 0
/
inspector.py
95 lines (87 loc) · 3.93 KB
/
inspector.py
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
from inspect import getargspec, ismethod
from itertools import izip
def getcallargs(func, *args, **kwds):
'''Get the actual value bounded to each formal parameter when calling
`func(*args,**kwds)`.
It works for methods too (bounded, unbounded, staticmethods, classmethods).
@returns: `(bindings, missing_args)`, where:
- `bindings` is a mapping of every formal parameter (including *varargs
and **kwargs if present) of the function to the respective bounded value.
- `missing_args` is a tuple of the formal parameters whose value was not
provided (i.e. using the respective default value)
Examples::
>>> def func(a, b='foo', c=None, *x, **y):
... pass
>>> getcallargs(func, 5)
({'a': 5, 'y': {}, 'c': None, 'b': 'foo', 'x': ()}, ('b', 'c'))
>>> getcallargs(func, 5, 'foo')
({'a': 5, 'y': {}, 'c': None, 'b': 'foo', 'x': ()}, ('c',))
>>> getcallargs(func, 5, c=['a', 'b'])
({'a': 5, 'y': {}, 'c': ['a', 'b'], 'b': 'foo', 'x': ()}, ('b',))
>>> getcallargs(func, 5, 6, 7, 8)
({'a': 5, 'y': {}, 'c': 7, 'b': 6, 'x': (8,)}, ())
>>> getcallargs(func, 5, z=3, b=2)
({'a': 5, 'y': {'z': 3}, 'c': None, 'b': 2, 'x': ()}, ('c',))
'''
arg2value = {}
f_name = func.func_name
spec_args, varargs, varkw, defaults = getargspec(func)
# handle methods
if ismethod(func):
# implicit 'self' (or 'cls' for classmethods) argument: func.im_self
if func.im_self is not None:
arg2value[spec_args.pop(0)] = func.im_self
elif not args or not isinstance(args[0], func.im_class):
got = args and ('%s instance' % type(args[0]).__name__) or 'nothing'
raise TypeError('unbound method %s() must be called with %s instance '
'as first argument (got %s instead)' %
(f_name, func.im_class.__name__, got))
num_args = len(args)
has_kwds = bool(kwds)
num_spec_args = len(spec_args)
num_defaults = len(defaults or ())
# get the expected arguments passed positionally
arg2value.update(izip(spec_args,args))
# get the expected arguments passed by name
for arg in spec_args:
if arg in kwds:
if arg in arg2value:
raise TypeError("%s() got multiple values for keyword "
"argument '%s'" % (f_name,arg))
else:
arg2value[arg] = kwds.pop(arg)
# fill in any missing values with the defaults
missing = []
if defaults:
for arg,val in izip(spec_args[-num_defaults:],defaults):
if arg not in arg2value:
arg2value[arg] = val
missing.append(arg)
# ensure that all required args have a value
for arg in spec_args:
if arg not in arg2value:
num_required = num_spec_args - num_defaults
raise TypeError('%s() takes at least %d %s argument%s (%d given)'
% (f_name, num_required,
has_kwds and 'non-keyword ' or '',
num_required>1 and 's' or '', num_args))
# handle any remaining named arguments
if varkw:
arg2value[varkw] = kwds
elif kwds:
raise TypeError("%s() got an unexpected keyword argument '%s'" %
(f_name, iter(kwds).next()))
# handle any remaining positional arguments
if varargs:
if num_args > num_spec_args:
arg2value[varargs] = args[-(num_args-num_spec_args):]
else:
arg2value[varargs] = ()
elif num_spec_args < num_args:
raise TypeError('%s() takes %s %d argument%s (%d given)' %
(f_name, defaults and 'at most' or 'exactly',
num_spec_args, num_spec_args>1 and 's' or '', num_args))
return arg2value, tuple(missing)
if __name__ == '__main__':
import doctest
doctest.testmod()