give the names of missing positional or keyword-only arguments (closes #12356)
This commit is contained in:
parent
947d6b0444
commit
e109c70860
|
@ -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,
|
||||
_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))
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
139
Python/ceval.c
139
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,17 +3308,19 @@ 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);
|
||||
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;
|
||||
else
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue