summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/inspect.py41
-rw-r--r--Lib/test/test_extcall.py107
-rw-r--r--Misc/NEWS4
-rw-r--r--Python/ceval.c141
4 files changed, 199 insertions, 94 deletions
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 @@ def formatargvalues(args, varargs, varkw, locals,
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 @@ def getcallargs(func, *positional, **named):
(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 @@ Verify clearing of SF bug #733667
>>> 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 @@ the function call setup. See <http://bugs.python.org/issue2016>.
>>> f(**x)
1 2
-Some additional tests about positional argument errors:
+Too many arguments:
- >>> def f(a, b):
- ... pass
- >>> f(b=1)
+ >>> def f(): pass
+ >>> f(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))
+ TypeError: f() takes 0 positional arguments but 1 was given
+ >>> def f(a): pass
+ >>> f(1, 2)
Traceback (most recent call last):
...
- TypeError: f() got multiple values for argument 'a'
- >>> def f(a, *, kw):
- ... pass
- >>> f(6, 4, 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 2 positional arguments (and 1 keyword-only argument) were given
-
- >>> def f(a):
- ... pass
- >>> f()
+ 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 1 positional argument but 0 were given
-
- >>> def f(a, b):
- ... pass
- >>> f(1)
+ 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 2 positional arguments but 1 was given
+ TypeError: f() takes from 1 to 2 positional arguments but 3 positional arguments (and 1 keyword-only argument) were given
- >>> def f(a, *b):
- ... 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
-
- >>> def f(a, *, kw=4):
- ... pass
- >>> f(kw=4)
+ TypeError: f() missing 1 required positional argument: 'a'
+ >>> def f(a, b): pass
+ >>> f()
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
+ 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 from 1 to 2 positional arguments but 0 were given
-
- >>> def f(a, *b):
- ... pass
+ 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() takes at least 1 positional argument but 0 were given
-
- >>> def f(*, kw):
- ... pass
- >>> f(3, kw=4)
+ 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() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
+ TypeError: f() missing 1 required positional argument: 'a'
- >>> def f(a, c=3, *b, kw):
- ... pass
+Same with keyword only args:
+
+ >>> def f(*, w): pass
>>> f()
Traceback (most recent call last):
- ...
- TypeError: f() takes at least 1 positional argument but 0 were given
- >>> f(kw=3)
- 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 1 required keyword-only argument: 'w'
+ >>> def f(*, a, b, c, d, e): 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 5 required keyword-only arguments: 'a', 'b', 'c', 'd', and 'e'
+
"""
import sys
diff --git a/Misc/NEWS b/Misc/NEWS
index fc1fa87..4e4b47c 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1?
Core and Builtins
-----------------
+- Issue #12356: When required positional or keyword-only arguments are not
+ given, produce a informative error message which includes the name(s) of the
+ missing arguments.
+
- Issue #12370: Fix super with not arguments when __class__ is overriden in the
class body.
diff --git a/Python/ceval.c b/Python/ceval.c
index f1f4c70..ac07070 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3046,28 +3046,118 @@ exit_eval_frame:
}
static void
-positional_argument_error(PyCodeObject *co, int given, int defcount, PyObject **fastlocals)
+format_missing(const char *kind, PyCodeObject *co, PyObject *names)
+{
+ int err;
+ Py_ssize_t len = PyList_GET_SIZE(names);
+ PyObject *name_str, *comma, *tail, *tmp;
+
+ assert(PyList_CheckExact(names));
+ assert(len >= 1);
+ /* Deal with the joys of natural language. */
+ switch (len) {
+ case 1:
+ name_str = PyList_GET_ITEM(names, 0);
+ Py_INCREF(name_str);
+ break;
+ case 2:
+ name_str = PyUnicode_FromFormat("%U and %U",
+ PyList_GET_ITEM(names, len - 2),
+ PyList_GET_ITEM(names, len - 1));
+ break;
+ default:
+ tail = PyUnicode_FromFormat(", %U, and %U",
+ PyList_GET_ITEM(names, len - 2),
+ PyList_GET_ITEM(names, len - 1));
+ /* Chop off the last two objects in the list. This shouldn't actually
+ fail, but we can't be too careful. */
+ err = PyList_SetSlice(names, len - 2, len, NULL);
+ if (err == -1) {
+ Py_DECREF(tail);
+ return;
+ }
+ /* Stitch everything up into a nice comma-separated list. */
+ comma = PyUnicode_FromString(", ");
+ if (comma == NULL) {
+ Py_DECREF(tail);
+ return;
+ }
+ tmp = PyUnicode_Join(comma, names);
+ Py_DECREF(comma);
+ if (tmp == NULL) {
+ Py_DECREF(tail);
+ return;
+ }
+ name_str = PyUnicode_Concat(tmp, tail);
+ Py_DECREF(tmp);
+ Py_DECREF(tail);
+ break;
+ }
+ if (name_str == NULL)
+ return;
+ PyErr_Format(PyExc_TypeError,
+ "%U() missing %i required %s argument%s: %U",
+ co->co_name,
+ len,
+ kind,
+ len == 1 ? "" : "s",
+ name_str);
+ Py_DECREF(name_str);
+}
+
+static void
+missing_arguments(PyCodeObject *co, int missing, int defcount,
+ PyObject **fastlocals)
+{
+ int i, j = 0;
+ int start, end;
+ int positional = defcount != -1;
+ const char *kind = positional ? "positional" : "keyword-only";
+ PyObject *missing_names;
+
+ /* Compute the names of the arguments that are missing. */
+ missing_names = PyList_New(missing);
+ if (missing_names == NULL)
+ return;
+ if (positional) {
+ start = 0;
+ end = co->co_argcount - defcount;
+ }
+ else {
+ start = co->co_argcount;
+ end = start + co->co_kwonlyargcount;
+ }
+ for (i = start; i < end; i++) {
+ if (GETLOCAL(i) == NULL) {
+ PyObject *raw = PyTuple_GET_ITEM(co->co_varnames, i);
+ PyObject *name = PyObject_Repr(raw);
+ if (name == NULL) {
+ Py_DECREF(missing_names);
+ return;
+ }
+ PyList_SET_ITEM(missing_names, j++, name);
+ }
+ }
+ assert(j == missing);
+ format_missing(kind, co, missing_names);
+ Py_DECREF(missing_names);
+}
+
+static void
+too_many_positional(PyCodeObject *co, int given, int defcount, PyObject **fastlocals)
{
int plural;
int kwonly_given = 0;
- int atleast = co->co_argcount - defcount;
int i;
PyObject *sig, *kwonly_sig;
- if (given == -1) {
- given = 0;
- for (i = 0; i < co->co_argcount; i++)
- if (GETLOCAL(i))
- given++;
- }
+ assert((co->co_flags & CO_VARARGS) == 0);
+ /* Count missing keyword-only args. */
for (i = co->co_argcount; i < co->co_argcount + co->co_kwonlyargcount; i++)
- if (GETLOCAL(i))
+ if (GETLOCAL(i) != NULL)
kwonly_given++;
- if (co->co_flags & CO_VARARGS) {
- plural = atleast != 1;
- sig = PyUnicode_FromFormat("at least %d", atleast);
- }
- else if (defcount) {
+ if (defcount) {
+ int atleast = co->co_argcount - defcount;
plural = 1;
sig = PyUnicode_FromFormat("from %d to %d", atleast, co->co_argcount);
}
@@ -3089,6 +3179,7 @@ positional_argument_error(PyCodeObject *co, int given, int defcount, PyObject **
else {
/* This will not fail. */
kwonly_sig = PyUnicode_FromString("");
+ assert(kwonly_sig != NULL);
}
PyErr_Format(PyExc_TypeError,
"%U() takes %U positional argument%s but %d%U %s given",
@@ -3217,16 +3308,18 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
SETLOCAL(j, value);
}
if (argcount > co->co_argcount && !(co->co_flags & CO_VARARGS)) {
- positional_argument_error(co, argcount, defcount, fastlocals);
+ too_many_positional(co, argcount, defcount, fastlocals);
goto fail;
}
if (argcount < co->co_argcount) {
int m = co->co_argcount - defcount;
- for (i = argcount; i < m; i++) {
- if (GETLOCAL(i) == NULL) {
- positional_argument_error(co, -1, defcount, fastlocals);
- goto fail;
- }
+ int missing = 0;
+ for (i = argcount; i < m; i++)
+ if (GETLOCAL(i) == NULL)
+ missing++;
+ if (missing) {
+ missing_arguments(co, missing, defcount, fastlocals);
+ goto fail;
}
if (n > m)
i = n - m;
@@ -3241,6 +3334,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
}
}
if (co->co_kwonlyargcount > 0) {
+ int missing = 0;
for (i = co->co_argcount; i < total_args; i++) {
PyObject *name;
if (GETLOCAL(i) != NULL)
@@ -3254,9 +3348,10 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
continue;
}
}
- PyErr_Format(PyExc_TypeError,
- "%U() requires keyword-only argument '%S'",
- co->co_name, name);
+ missing++;
+ }
+ if (missing) {
+ missing_arguments(co, missing, -1, fastlocals);
goto fail;
}
}