Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 25cd7eb

Browse files
committed
Merged revisions 79500 via svnmerge from
svn+ssh://[email protected]/python/trunk ........ r79500 | benjamin.peterson | 2010-03-30 12:58:13 -0500 (Tue, 30 Mar 2010) | 4 lines add inspect.getcallargs, which binds function arguments like a normal call #3135 Patch by George Sakkis ........
1 parent e6c9d24 commit 25cd7eb

3 files changed

Lines changed: 270 additions & 4 deletions

File tree

Doc/library/inspect.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,32 @@ Classes and functions
449449
metatype is in use, cls will be the first element of the tuple.
450450

451451

452+
.. function:: getcallargs(func[, *args][, **kwds])
453+
454+
Bind the *args* and *kwds* to the argument names of the Python function or
455+
method *func*, as if it was called with them. For bound methods, bind also the
456+
first argument (typically named ``self``) to the associated instance. A dict
457+
is returned, mapping the argument names (including the names of the ``*`` and
458+
``**`` arguments, if any) to their values from *args* and *kwds*. In case of
459+
invoking *func* incorrectly, i.e. whenever ``func(*args, **kwds)`` would raise
460+
an exception because of incompatible signature, an exception of the same type
461+
and the same or similar message is raised. For example::
462+
463+
>>> from inspect import getcallargs
464+
>>> def f(a, b=1, *pos, **named):
465+
... pass
466+
>>> getcallargs(f, 1, 2, 3)
467+
{'a': 1, 'named': {}, 'b': 2, 'pos': (3,)}
468+
>>> getcallargs(f, a=2, x=4)
469+
{'a': 2, 'named': {'x': 4}, 'b': 1, 'pos': ()}
470+
>>> getcallargs(f)
471+
Traceback (most recent call last):
472+
...
473+
TypeError: f() takes at least 1 argument (0 given)
474+
475+
.. versionadded:: 3.2
476+
477+
452478
.. _inspect-stack:
453479

454480
The interpreter stack

Lib/inspect.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
getmodule() - determine the module that an object came from
1818
getclasstree() - arrange classes so as to represent their hierarchy
1919
20-
getargspec(), getargvalues() - get info about function arguments
20+
getargspec(), getargvalues(), getcallargs() - get info about function arguments
2121
getfullargspec() - same, with support for Python-3000 features
2222
formatargspec(), formatargvalues() - format an argument spec
2323
getouterframes(), getinnerframes() - get info about frames
@@ -33,6 +33,7 @@
3333
import sys
3434
import os
3535
import types
36+
import itertools
3637
import string
3738
import re
3839
import dis
@@ -926,6 +927,71 @@ def convert(name, locals=locals,
926927
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
927928
return '(' + ', '.join(specs) + ')'
928929

930+
def getcallargs(func, *positional, **named):
931+
"""Get the mapping of arguments to values.
932+
933+
A dict is returned, with keys the function argument names (including the
934+
names of the * and ** arguments, if any), and values the respective bound
935+
values from 'positional' and 'named'."""
936+
spec = getfullargspec(func)
937+
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
938+
f_name = func.__name__
939+
arg2value = {}
940+
941+
if ismethod(func) and func.__self__ is not None:
942+
# implicit 'self' (or 'cls' for classmethods) argument
943+
positional = (func.__self__,) + positional
944+
num_pos = len(positional)
945+
num_total = num_pos + len(named)
946+
num_args = len(args)
947+
num_defaults = len(defaults) if defaults else 0
948+
for arg, value in zip(args, positional):
949+
arg2value[arg] = value
950+
if varargs:
951+
if num_pos > num_args:
952+
arg2value[varargs] = positional[-(num_pos-num_args):]
953+
else:
954+
arg2value[varargs] = ()
955+
elif 0 < num_args < num_pos:
956+
raise TypeError('%s() takes %s %d %s (%d given)' % (
957+
f_name, 'at most' if defaults else 'exactly', num_args,
958+
'arguments' if num_args > 1 else 'argument', num_total))
959+
elif num_args == 0 and num_total:
960+
raise TypeError('%s() takes no arguments (%d given)' %
961+
(f_name, num_total))
962+
963+
for arg in itertools.chain(args, kwonlyargs):
964+
if arg in named:
965+
if arg in arg2value:
966+
raise TypeError("%s() got multiple values for keyword "
967+
"argument '%s'" % (f_name, arg))
968+
else:
969+
arg2value[arg] = named.pop(arg)
970+
for kwonlyarg in kwonlyargs:
971+
if kwonlyarg not in arg2value:
972+
try:
973+
arg2value[kwonlyarg] = kwonlydefaults[kwonlyarg]
974+
except KeyError:
975+
raise TypeError("%s() needs keyword-only argument %s" %
976+
(f_name, kwonlyarg))
977+
if defaults: # fill in any missing values with the defaults
978+
for arg, value in zip(args[-num_defaults:], defaults):
979+
if arg not in arg2value:
980+
arg2value[arg] = value
981+
if varkw:
982+
arg2value[varkw] = named
983+
elif named:
984+
unexpected = next(iter(named))
985+
raise TypeError("%s() got an unexpected keyword argument '%s'" %
986+
(f_name, unexpected))
987+
unassigned = num_args - len([arg for arg in args if arg in arg2value])
988+
if unassigned:
989+
num_required = num_args - num_defaults
990+
raise TypeError('%s() takes %s %d %s (%d given)' % (
991+
f_name, 'at least' if defaults else 'exactly', num_required,
992+
'arguments' if num_required > 1 else 'argument', num_total))
993+
return arg2value
994+
929995
# -------------------------------------------------- stack frame extraction
930996

931997
Traceback = namedtuple('Traceback', 'filename lineno function code_context index')

Lib/test/test_inspect.py

Lines changed: 177 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import sys
23
import types
34
import unittest
@@ -519,10 +520,183 @@ def m1(self): pass
519520
self.assertIn(('m1', 'method', D), attrs, 'missing plain method')
520521
self.assertIn(('datablob', 'data', A), attrs, 'missing data')
521522

523+
class TestGetcallargsFunctions(unittest.TestCase):
524+
525+
def assertEqualCallArgs(self, func, call_params_string, locs=None):
526+
locs = dict(locs or {}, func=func)
527+
r1 = eval('func(%s)' % call_params_string, None, locs)
528+
r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None,
529+
locs)
530+
self.assertEqual(r1, r2)
531+
532+
def assertEqualException(self, func, call_param_string, locs=None):
533+
locs = dict(locs or {}, func=func)
534+
try:
535+
eval('func(%s)' % call_param_string, None, locs)
536+
except Exception as e:
537+
ex1 = e
538+
else:
539+
self.fail('Exception not raised')
540+
try:
541+
eval('inspect.getcallargs(func, %s)' % call_param_string, None,
542+
locs)
543+
except Exception as e:
544+
ex2 = e
545+
else:
546+
self.fail('Exception not raised')
547+
self.assertIs(type(ex1), type(ex2))
548+
self.assertEqual(str(ex1), str(ex2))
549+
del ex1, ex2
550+
551+
def makeCallable(self, signature):
552+
"""Create a function that returns its locals()"""
553+
code = "lambda %s: locals()"
554+
return eval(code % signature)
555+
556+
def test_plain(self):
557+
f = self.makeCallable('a, b=1')
558+
self.assertEqualCallArgs(f, '2')
559+
self.assertEqualCallArgs(f, '2, 3')
560+
self.assertEqualCallArgs(f, 'a=2')
561+
self.assertEqualCallArgs(f, 'b=3, a=2')
562+
self.assertEqualCallArgs(f, '2, b=3')
563+
# expand *iterable / **mapping
564+
self.assertEqualCallArgs(f, '*(2,)')
565+
self.assertEqualCallArgs(f, '*[2]')
566+
self.assertEqualCallArgs(f, '*(2, 3)')
567+
self.assertEqualCallArgs(f, '*[2, 3]')
568+
self.assertEqualCallArgs(f, '**{"a":2}')
569+
self.assertEqualCallArgs(f, 'b=3, **{"a":2}')
570+
self.assertEqualCallArgs(f, '2, **{"b":3}')
571+
self.assertEqualCallArgs(f, '**{"b":3, "a":2}')
572+
# expand UserList / UserDict
573+
self.assertEqualCallArgs(f, '*collections.UserList([2])')
574+
self.assertEqualCallArgs(f, '*collections.UserList([2, 3])')
575+
self.assertEqualCallArgs(f, '**collections.UserDict(a=2)')
576+
self.assertEqualCallArgs(f, '2, **collections.UserDict(b=3)')
577+
self.assertEqualCallArgs(f, 'b=2, **collections.UserDict(a=3)')
578+
579+
def test_varargs(self):
580+
f = self.makeCallable('a, b=1, *c')
581+
self.assertEqualCallArgs(f, '2')
582+
self.assertEqualCallArgs(f, '2, 3')
583+
self.assertEqualCallArgs(f, '2, 3, 4')
584+
self.assertEqualCallArgs(f, '*(2,3,4)')
585+
self.assertEqualCallArgs(f, '2, *[3,4]')
586+
self.assertEqualCallArgs(f, '2, 3, *collections.UserList([4])')
587+
588+
def test_varkw(self):
589+
f = self.makeCallable('a, b=1, **c')
590+
self.assertEqualCallArgs(f, 'a=2')
591+
self.assertEqualCallArgs(f, '2, b=3, c=4')
592+
self.assertEqualCallArgs(f, 'b=3, a=2, c=4')
593+
self.assertEqualCallArgs(f, 'c=4, **{"a":2, "b":3}')
594+
self.assertEqualCallArgs(f, '2, c=4, **{"b":3}')
595+
self.assertEqualCallArgs(f, 'b=2, **{"a":3, "c":4}')
596+
self.assertEqualCallArgs(f, '**collections.UserDict(a=2, b=3, c=4)')
597+
self.assertEqualCallArgs(f, '2, c=4, **collections.UserDict(b=3)')
598+
self.assertEqualCallArgs(f, 'b=2, **collections.UserDict(a=3, c=4)')
599+
600+
def test_keyword_only(self):
601+
f = self.makeCallable('a=3, *, c, d=2')
602+
self.assertEqualCallArgs(f, 'c=3')
603+
self.assertEqualCallArgs(f, 'c=3, a=3')
604+
self.assertEqualCallArgs(f, 'a=2, c=4')
605+
self.assertEqualCallArgs(f, '4, c=4')
606+
self.assertEqualException(f, '')
607+
self.assertEqualException(f, '3')
608+
self.assertEqualException(f, 'a=3')
609+
self.assertEqualException(f, 'd=4')
610+
611+
def test_multiple_features(self):
612+
f = self.makeCallable('a, b=2, *f, **g')
613+
self.assertEqualCallArgs(f, '2, 3, 7')
614+
self.assertEqualCallArgs(f, '2, 3, x=8')
615+
self.assertEqualCallArgs(f, '2, 3, x=8, *[(4,[5,6]), 7]')
616+
self.assertEqualCallArgs(f, '2, x=8, *[3, (4,[5,6]), 7], y=9')
617+
self.assertEqualCallArgs(f, 'x=8, *[2, 3, (4,[5,6])], y=9')
618+
self.assertEqualCallArgs(f, 'x=8, *collections.UserList('
619+
'[2, 3, (4,[5,6])]), **{"y":9, "z":10}')
620+
self.assertEqualCallArgs(f, '2, x=8, *collections.UserList([3, '
621+
'(4,[5,6])]), **collections.UserDict('
622+
'y=9, z=10)')
623+
624+
def test_errors(self):
625+
f0 = self.makeCallable('')
626+
f1 = self.makeCallable('a, b')
627+
f2 = self.makeCallable('a, b=1')
628+
# f0 takes no arguments
629+
self.assertEqualException(f0, '1')
630+
self.assertEqualException(f0, 'x=1')
631+
self.assertEqualException(f0, '1,x=1')
632+
# f1 takes exactly 2 arguments
633+
self.assertEqualException(f1, '')
634+
self.assertEqualException(f1, '1')
635+
self.assertEqualException(f1, 'a=2')
636+
self.assertEqualException(f1, 'b=3')
637+
# f2 takes at least 1 argument
638+
self.assertEqualException(f2, '')
639+
self.assertEqualException(f2, 'b=3')
640+
for f in f1, f2:
641+
# f1/f2 takes exactly/at most 2 arguments
642+
self.assertEqualException(f, '2, 3, 4')
643+
self.assertEqualException(f, '1, 2, 3, a=1')
644+
self.assertEqualException(f, '2, 3, 4, c=5')
645+
self.assertEqualException(f, '2, 3, 4, a=1, c=5')
646+
# f got an unexpected keyword argument
647+
self.assertEqualException(f, 'c=2')
648+
self.assertEqualException(f, '2, c=3')
649+
self.assertEqualException(f, '2, 3, c=4')
650+
self.assertEqualException(f, '2, c=4, b=3')
651+
self.assertEqualException(f, '**{u"\u03c0\u03b9": 4}')
652+
# f got multiple values for keyword argument
653+
self.assertEqualException(f, '1, a=2')
654+
self.assertEqualException(f, '1, **{"a":2}')
655+
self.assertEqualException(f, '1, 2, b=3')
656+
# XXX: Python inconsistency
657+
# - for functions and bound methods: unexpected keyword 'c'
658+
# - for unbound methods: multiple values for keyword 'a'
659+
#self.assertEqualException(f, '1, c=3, a=2')
660+
661+
class TestGetcallargsMethods(TestGetcallargsFunctions):
662+
663+
def setUp(self):
664+
class Foo(object):
665+
pass
666+
self.cls = Foo
667+
self.inst = Foo()
668+
669+
def makeCallable(self, signature):
670+
assert 'self' not in signature
671+
mk = super(TestGetcallargsMethods, self).makeCallable
672+
self.cls.method = mk('self, ' + signature)
673+
return self.inst.method
674+
675+
class TestGetcallargsUnboundMethods(TestGetcallargsMethods):
676+
677+
def makeCallable(self, signature):
678+
super(TestGetcallargsUnboundMethods, self).makeCallable(signature)
679+
return self.cls.method
680+
681+
def assertEqualCallArgs(self, func, call_params_string, locs=None):
682+
return super(TestGetcallargsUnboundMethods, self).assertEqualCallArgs(
683+
*self._getAssertEqualParams(func, call_params_string, locs))
684+
685+
def assertEqualException(self, func, call_params_string, locs=None):
686+
return super(TestGetcallargsUnboundMethods, self).assertEqualException(
687+
*self._getAssertEqualParams(func, call_params_string, locs))
688+
689+
def _getAssertEqualParams(self, func, call_params_string, locs=None):
690+
assert 'inst' not in call_params_string
691+
locs = dict(locs or {}, inst=self.inst)
692+
return (func, 'inst,' + call_params_string, locs)
693+
522694
def test_main():
523-
run_unittest(TestDecorators, TestRetrievingSourceCode, TestOneliners,
524-
TestBuggyCases,
525-
TestInterpreterStack, TestClassesAndFunctions, TestPredicates)
695+
run_unittest(
696+
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
697+
TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
698+
TestGetcallargsFunctions, TestGetcallargsMethods,
699+
TestGetcallargsUnboundMethods)
526700

527701
if __name__ == "__main__":
528702
test_main()

0 commit comments

Comments
 (0)