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

Skip to content

Commit b204a42

Browse files
committed
greatly improve argument parsing error messages (closes #12265)
1 parent 40b408d commit b204a42

5 files changed

Lines changed: 301 additions & 198 deletions

File tree

Lib/inspect.py

Lines changed: 59 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,29 @@ def convert(name, locals=locals,
914914
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
915915
return '(' + ', '.join(specs) + ')'
916916

917+
def _positional_error(f_name, args, kwonly, varargs, defcount, given, values):
918+
atleast = len(args) - defcount
919+
if given is None:
920+
given = len([arg for arg in args if arg in values])
921+
kwonly_given = len([arg for arg in kwonly if arg in values])
922+
if varargs:
923+
plural = atleast != 1
924+
sig = "at least %d" % (atleast,)
925+
elif defcount:
926+
plural = True
927+
sig = "from %d to %d" % (atleast, len(args))
928+
else:
929+
plural = len(args) != 1
930+
sig = str(len(args))
931+
kwonly_sig = ""
932+
if kwonly_given:
933+
msg = " positional argument%s (and %d keyword-only argument%s)"
934+
kwonly_sig = (msg % ("s" if given != 1 else "", kwonly_given,
935+
"s" if kwonly_given != 1 else ""))
936+
raise TypeError("%s() takes %s positional argument%s but %d%s %s given" %
937+
(f_name, sig, "s" if plural else "", given, kwonly_sig,
938+
"was" if given == 1 and not kwonly_given else "were"))
939+
917940
def getcallargs(func, *positional, **named):
918941
"""Get the mapping of arguments to values.
919942
@@ -925,64 +948,50 @@ def getcallargs(func, *positional, **named):
925948
f_name = func.__name__
926949
arg2value = {}
927950

951+
928952
if ismethod(func) and func.__self__ is not None:
929953
# implicit 'self' (or 'cls' for classmethods) argument
930954
positional = (func.__self__,) + positional
931955
num_pos = len(positional)
932-
num_total = num_pos + len(named)
933956
num_args = len(args)
934957
num_defaults = len(defaults) if defaults else 0
935-
for arg, value in zip(args, positional):
936-
arg2value[arg] = value
958+
959+
n = min(num_pos, num_args)
960+
for i in range(n):
961+
arg2value[args[i]] = positional[i]
937962
if varargs:
938-
if num_pos > num_args:
939-
arg2value[varargs] = positional[-(num_pos-num_args):]
940-
else:
941-
arg2value[varargs] = ()
942-
elif 0 < num_args < num_pos:
943-
raise TypeError('%s() takes %s %d positional %s (%d given)' % (
944-
f_name, 'at most' if defaults else 'exactly', num_args,
945-
'arguments' if num_args > 1 else 'argument', num_total))
946-
elif num_args == 0 and num_total:
947-
if varkw or kwonlyargs:
948-
if num_pos:
949-
# XXX: We should use num_pos, but Python also uses num_total:
950-
raise TypeError('%s() takes exactly 0 positional arguments '
951-
'(%d given)' % (f_name, num_total))
952-
else:
953-
raise TypeError('%s() takes no arguments (%d given)' %
954-
(f_name, num_total))
955-
956-
for arg in itertools.chain(args, kwonlyargs):
957-
if arg in named:
958-
if arg in arg2value:
959-
raise TypeError("%s() got multiple values for keyword "
960-
"argument '%s'" % (f_name, arg))
961-
else:
962-
arg2value[arg] = named.pop(arg)
963-
for kwonlyarg in kwonlyargs:
964-
if kwonlyarg not in arg2value:
965-
try:
966-
arg2value[kwonlyarg] = kwonlydefaults[kwonlyarg]
967-
except KeyError:
968-
raise TypeError("%s() needs keyword-only argument %s" %
969-
(f_name, kwonlyarg))
970-
if defaults: # fill in any missing values with the defaults
971-
for arg, value in zip(args[-num_defaults:], defaults):
972-
if arg not in arg2value:
973-
arg2value[arg] = value
963+
arg2value[varargs] = tuple(positional[n:])
964+
possible_kwargs = set(args + kwonlyargs)
974965
if varkw:
975-
arg2value[varkw] = named
976-
elif named:
977-
unexpected = next(iter(named))
978-
raise TypeError("%s() got an unexpected keyword argument '%s'" %
979-
(f_name, unexpected))
980-
unassigned = num_args - len([arg for arg in args if arg in arg2value])
981-
if unassigned:
982-
num_required = num_args - num_defaults
983-
raise TypeError('%s() takes %s %d %s (%d given)' % (
984-
f_name, 'at least' if defaults else 'exactly', num_required,
985-
'arguments' if num_required > 1 else 'argument', num_total))
966+
arg2value[varkw] = {}
967+
for kw, value in named.items():
968+
if kw not in possible_kwargs:
969+
if not varkw:
970+
raise TypeError("%s() got an unexpected keyword argument %r" %
971+
(f_name, kw))
972+
arg2value[varkw][kw] = value
973+
continue
974+
if kw in arg2value:
975+
raise TypeError("%s() got multiple values for argument %r" %
976+
(f_name, kw))
977+
arg2value[kw] = value
978+
if num_pos > num_args and not varargs:
979+
_positional_error(f_name, args, kwonlyargs, varargs, num_defaults,
980+
num_pos, arg2value)
981+
if num_pos < num_args:
982+
for arg in args[:num_args - num_defaults]:
983+
if arg not in arg2value:
984+
_positional_error(f_name, args, kwonlyargs, varargs,
985+
num_defaults, None, arg2value)
986+
for i, arg in enumerate(args[num_args - num_defaults:]):
987+
if arg not in arg2value:
988+
arg2value[arg] = defaults[i]
989+
for kwarg in kwonlyargs:
990+
if kwarg not in arg2value:
991+
if kwarg not in kwonlydefaults:
992+
raise TypeError("%s() requires keyword-only argument %r" %
993+
(f_name, kwarg))
994+
arg2value[kwarg] = kwonlydefaults[kwarg]
986995
return arg2value
987996

988997
# -------------------------------------------------- stack frame extraction

Lib/test/test_extcall.py

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,17 @@
6666
>>> g()
6767
Traceback (most recent call last):
6868
...
69-
TypeError: g() takes at least 1 argument (0 given)
69+
TypeError: g() takes at least 1 positional argument but 0 were given
7070
7171
>>> g(*())
7272
Traceback (most recent call last):
7373
...
74-
TypeError: g() takes at least 1 argument (0 given)
74+
TypeError: g() takes at least 1 positional argument but 0 were given
7575
7676
>>> g(*(), **{})
7777
Traceback (most recent call last):
7878
...
79-
TypeError: g() takes at least 1 argument (0 given)
79+
TypeError: g() takes at least 1 positional argument but 0 were given
8080
8181
>>> g(1)
8282
1 () {}
@@ -151,7 +151,7 @@
151151
>>> g(1, 2, 3, **{'x': 4, 'y': 5})
152152
Traceback (most recent call last):
153153
...
154-
TypeError: g() got multiple values for keyword argument 'x'
154+
TypeError: g() got multiple values for argument 'x'
155155
156156
>>> f(**{1:2})
157157
Traceback (most recent call last):
@@ -263,29 +263,91 @@
263263
>>> f(**x)
264264
1 2
265265
266-
A obscure message:
266+
Some additional tests about positional argument errors:
267267
268268
>>> def f(a, b):
269269
... pass
270270
>>> f(b=1)
271271
Traceback (most recent call last):
272272
...
273-
TypeError: f() takes exactly 2 arguments (1 given)
274-
275-
The number of arguments passed in includes keywords:
273+
TypeError: f() takes 2 positional arguments but 1 was given
276274
277275
>>> def f(a):
278276
... pass
279277
>>> f(6, a=4, *(1, 2, 3))
280278
Traceback (most recent call last):
281279
...
282-
TypeError: f() takes exactly 1 positional argument (5 given)
280+
TypeError: f() got multiple values for argument 'a'
283281
>>> def f(a, *, kw):
284282
... pass
285283
>>> f(6, 4, kw=4)
286284
Traceback (most recent call last):
287285
...
288-
TypeError: f() takes exactly 1 positional argument (3 given)
286+
TypeError: f() takes 1 positional argument but 2 positional arguments (and 1 keyword-only argument) were given
287+
288+
>>> def f(a):
289+
... pass
290+
>>> f()
291+
Traceback (most recent call last):
292+
...
293+
TypeError: f() takes 1 positional argument but 0 were given
294+
295+
>>> def f(a, b):
296+
... pass
297+
>>> f(1)
298+
Traceback (most recent call last):
299+
...
300+
TypeError: f() takes 2 positional arguments but 1 was given
301+
302+
>>> def f(a, *b):
303+
... pass
304+
>>> f()
305+
Traceback (most recent call last):
306+
...
307+
TypeError: f() takes at least 1 positional argument but 0 were given
308+
309+
>>> def f(a, *, kw=4):
310+
... pass
311+
>>> f(kw=4)
312+
Traceback (most recent call last):
313+
...
314+
TypeError: f() takes 1 positional argument but 0 positional arguments (and 1 keyword-only argument) were given
315+
316+
>>> def f(a, b=2):
317+
... pass
318+
>>> f()
319+
Traceback (most recent call last):
320+
...
321+
TypeError: f() takes from 1 to 2 positional arguments but 0 were given
322+
323+
>>> def f(a, *b):
324+
... pass
325+
>>> f()
326+
Traceback (most recent call last):
327+
...
328+
TypeError: f() takes at least 1 positional argument but 0 were given
329+
330+
>>> def f(*, kw):
331+
... pass
332+
>>> f(3, kw=4)
333+
Traceback (most recent call last):
334+
...
335+
TypeError: f() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
336+
337+
>>> def f(a, c=3, *b, kw):
338+
... pass
339+
>>> f()
340+
Traceback (most recent call last):
341+
...
342+
TypeError: f() takes at least 1 positional argument but 0 were given
343+
>>> f(kw=3)
344+
Traceback (most recent call last):
345+
...
346+
TypeError: f() takes at least 1 positional argument but 0 positional arguments (and 1 keyword-only argument) were given
347+
>>> f(kw=3, c=4)
348+
Traceback (most recent call last):
349+
...
350+
TypeError: f() takes at least 1 positional argument but 1 positional argument (and 1 keyword-only argument) were given
289351
"""
290352

291353
import sys

Lib/test/test_keywordonlyarg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def f(a, b=None, *, c=None):
7878
pass
7979
with self.assertRaises(TypeError) as exc:
8080
f(1, 2, 3)
81-
expected = "f() takes at most 2 positional arguments (3 given)"
81+
expected = "f() takes from 1 to 2 positional arguments but 3 were given"
8282
self.assertEqual(str(exc.exception), expected)
8383

8484
def testSyntaxErrorForFunctionCall(self):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #12265: Make error messages produced by passing an invalid set of
14+
arguments to a function more informative.
15+
1316
- Issue #12225: Still allow Python to build if Python is not in its hg repo or
1417
mercurial is not installed.
1518

0 commit comments

Comments
 (0)