give the names of missing positional or keyword-only arguments (closes #12356)
diff --git a/Lib/inspect.py b/Lib/inspect.py
index aa4c30f..80802e4 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -918,10 +918,24 @@
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
return '(' + ', '.join(specs) + ')'
-def _positional_error(f_name, args, kwonly, varargs, defcount, given, values):
+def _missing_arguments(f_name, argnames, pos, values):
+ names = [repr(name) for name in argnames if name not in values]
+ missing = len(names)
+ if missing == 1:
+ s = names[0]
+ elif missing == 2:
+ s = "{} and {}".format(*names)
+ else:
+ tail = ", {} and {}".format(names[-2:])
+ del names[-2:]
+ s = ", ".join(names) + tail
+ raise TypeError("%s() missing %i required %s argument%s: %s" %
+ (f_name, missing,
+ "positional" if pos else "keyword-only",
+ "" if missing == 1 else "s", s))
+
+def _too_many(f_name, args, kwonly, varargs, defcount, given, values):
atleast = len(args) - defcount
- if given is None:
- given = len([arg for arg in args if arg in values])
kwonly_given = len([arg for arg in kwonly if arg in values])
if varargs:
plural = atleast != 1
@@ -980,22 +994,25 @@
(f_name, kw))
arg2value[kw] = value
if num_pos > num_args and not varargs:
- _positional_error(f_name, args, kwonlyargs, varargs, num_defaults,
- num_pos, arg2value)
+ _too_many(f_name, args, kwonlyargs, varargs, num_defaults,
+ num_pos, arg2value)
if num_pos < num_args:
- for arg in args[:num_args - num_defaults]:
+ req = args[:num_args - num_defaults]
+ for arg in req:
if arg not in arg2value:
- _positional_error(f_name, args, kwonlyargs, varargs,
- num_defaults, None, arg2value)
+ _missing_arguments(f_name, req, True, arg2value)
for i, arg in enumerate(args[num_args - num_defaults:]):
if arg not in arg2value:
arg2value[arg] = defaults[i]
+ missing = 0
for kwarg in kwonlyargs:
if kwarg not in arg2value:
- if kwarg not in kwonlydefaults:
- raise TypeError("%s() requires keyword-only argument %r" %
- (f_name, kwarg))
- arg2value[kwarg] = kwonlydefaults[kwarg]
+ if kwarg in kwonlydefaults:
+ arg2value[kwarg] = kwonlydefaults[kwarg]
+ else:
+ missing += 1
+ if missing:
+ _missing_arguments(f_name, kwonlyargs, False, arg2value)
return arg2value
# -------------------------------------------------- stack frame extraction
diff --git a/Lib/test/test_extcall.py b/Lib/test/test_extcall.py
index 49d5441..6b6c12d 100644
--- a/Lib/test/test_extcall.py
+++ b/Lib/test/test_extcall.py
@@ -66,17 +66,17 @@
>>> g()
Traceback (most recent call last):
...
- TypeError: g() takes at least 1 positional argument but 0 were given
+ TypeError: g() missing 1 required positional argument: 'x'
>>> g(*())
Traceback (most recent call last):
...
- TypeError: g() takes at least 1 positional argument but 0 were given
+ TypeError: g() missing 1 required positional argument: 'x'
>>> g(*(), **{})
Traceback (most recent call last):
...
- TypeError: g() takes at least 1 positional argument but 0 were given
+ TypeError: g() missing 1 required positional argument: 'x'
>>> g(1)
1 () {}
@@ -263,91 +263,80 @@
>>> f(**x)
1 2
-Some additional tests about positional argument errors:
+Too many arguments:
- >>> def f(a, b):
- ... pass
- >>> f(b=1)
- Traceback (most recent call last):
- ...
- TypeError: f() takes 2 positional arguments but 1 was given
-
- >>> def f(a):
- ... pass
- >>> f(6, a=4, *(1, 2, 3))
- Traceback (most recent call last):
- ...
- TypeError: f() got multiple values for argument 'a'
- >>> def f(a, *, kw):
- ... pass
- >>> f(6, 4, kw=4)
- Traceback (most recent call last):
- ...
- TypeError: f() takes 1 positional argument but 2 positional arguments (and 1 keyword-only argument) were given
-
- >>> def f(a):
- ... pass
- >>> f()
- Traceback (most recent call last):
- ...
- TypeError: f() takes 1 positional argument but 0 were given
-
- >>> def f(a, b):
- ... pass
+ >>> def f(): pass
>>> f(1)
Traceback (most recent call last):
...
- TypeError: f() takes 2 positional arguments but 1 was given
-
- >>> def f(a, *b):
- ... pass
- >>> f()
+ TypeError: f() takes 0 positional arguments but 1 was given
+ >>> def f(a): pass
+ >>> f(1, 2)
Traceback (most recent call last):
...
- TypeError: f() takes at least 1 positional argument but 0 were given
-
- >>> def f(a, *, kw=4):
- ... pass
- >>> f(kw=4)
+ TypeError: f() takes 1 positional argument but 2 were given
+ >>> def f(a, b=1): pass
+ >>> f(1, 2, 3)
Traceback (most recent call last):
...
- TypeError: f() takes 1 positional argument but 0 positional arguments (and 1 keyword-only argument) were given
-
- >>> def f(a, b=2):
- ... pass
- >>> f()
- Traceback (most recent call last):
- ...
- TypeError: f() takes from 1 to 2 positional arguments but 0 were given
-
- >>> def f(a, *b):
- ... pass
- >>> f()
- Traceback (most recent call last):
- ...
- TypeError: f() takes at least 1 positional argument but 0 were given
-
- >>> def f(*, kw):
- ... pass
- >>> f(3, kw=4)
+ TypeError: f() takes from 1 to 2 positional arguments but 3 were given
+ >>> def f(*, kw): pass
+ >>> f(1, kw=3)
Traceback (most recent call last):
...
TypeError: f() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
+ >>> def f(*, kw, b): pass
+ >>> f(1, 2, 3, b=3, kw=3)
+ Traceback (most recent call last):
+ ...
+ TypeError: f() takes 0 positional arguments but 3 positional arguments (and 2 keyword-only arguments) were given
+ >>> def f(a, b=2, *, kw): pass
+ >>> f(2, 3, 4, kw=4)
+ Traceback (most recent call last):
+ ...
+ TypeError: f() takes from 1 to 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
- >>> def f(a, c=3, *b, kw):
- ... pass
+Too few and missing arguments:
+
+ >>> def f(a): pass
>>> f()
Traceback (most recent call last):
- ...
- TypeError: f() takes at least 1 positional argument but 0 were given
- >>> f(kw=3)
+ ...
+ TypeError: f() missing 1 required positional argument: 'a'
+ >>> def f(a, b): pass
+ >>> f()
Traceback (most recent call last):
- ...
- TypeError: f() takes at least 1 positional argument but 0 positional arguments (and 1 keyword-only argument) were given
- >>> f(kw=3, c=4)
+ ...
+ TypeError: f() missing 2 required positional arguments: 'a' and 'b'
+ >>> def f(a, b, c): pass
+ >>> f()
Traceback (most recent call last):
- ...
- TypeError: f() takes at least 1 positional argument but 1 positional argument (and 1 keyword-only argument) were given
+ ...
+ TypeError: f() missing 3 required positional arguments: 'a', 'b', and 'c'
+ >>> def f(a, b, c, d, e): pass
+ >>> f()
+ Traceback (most recent call last):
+ ...
+ TypeError: f() missing 5 required positional arguments: 'a', 'b', 'c', 'd', and 'e'
+ >>> def f(a, b=4, c=5, d=5): pass
+ >>> f(c=12, b=9)
+ Traceback (most recent call last):
+ ...
+ TypeError: f() missing 1 required positional argument: 'a'
+
+Same with keyword only args:
+
+ >>> def f(*, w): pass
+ >>> f()
+ Traceback (most recent call last):
+ ...
+ TypeError: f() missing 1 required keyword-only argument: 'w'
+ >>> def f(*, a, b, c, d, e): pass
+ >>> f()
+ Traceback (most recent call last):
+ ...
+ TypeError: f() missing 5 required keyword-only arguments: 'a', 'b', 'c', 'd', and 'e'
+
"""
import sys