Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ def convert(name, locals=locals,
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
return '(' + ', '.join(specs) + ')'

def _missing_arguments(f_name, argnames, pos, values):
def _missing_arguments(f_name, argnames, pos, values, *, has_posonly=False):
names = [repr(name) for name in argnames if name not in values]
missing = len(names)
if missing == 1:
Expand All @@ -1392,10 +1392,18 @@ def _missing_arguments(f_name, argnames, pos, values):
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))

if pos:
qualifier = "positional" if has_posonly else ""
else:
qualifier = "keyword-only"
if qualifier:
raise TypeError("%s() missing %i required %s argument%s: %s" %
(f_name, missing, qualifier,
"" if missing == 1 else "s", s))
else:
raise TypeError("%s() missing %i required argument%s: %s" %
(f_name, missing, "" if missing == 1 else "s", s))

def _too_many(f_name, args, kwonly, varargs, defcount, given, values):
atleast = len(args) - defcount
Expand Down Expand Up @@ -1429,7 +1437,9 @@ def getcallargs(func, /, *positional, **named):
f_name = func.__name__
arg2value = {}


num_posonly = 0
if hasattr(func, '__code__'):
num_posonly = func.__code__.co_posonlyargcount
if ismethod(func) and func.__self__ is not None:
# implicit 'self' (or 'cls' for classmethods) argument
positional = (func.__self__,) + positional
Expand Down Expand Up @@ -1463,7 +1473,9 @@ def getcallargs(func, /, *positional, **named):
req = args[:num_args - num_defaults]
for arg in req:
if arg not in arg2value:
_missing_arguments(f_name, req, True, arg2value)
missing_posonly = any(i < num_posonly for i, name in enumerate(args)
if name in req and name not in arg2value)
_missing_arguments(f_name, req, True, arg2value, has_posonly=missing_posonly)
for i, arg in enumerate(args[num_args - num_defaults:]):
if arg not in arg2value:
arg2value[arg] = defaults[i]
Expand All @@ -1475,7 +1487,7 @@ def getcallargs(func, /, *positional, **named):
else:
missing += 1
if missing:
_missing_arguments(f_name, kwonlyargs, False, arg2value)
_missing_arguments(f_name, kwonlyargs, False, arg2value, has_posonly=False)
return arg2value

ClosureVars = namedtuple('ClosureVars', 'nonlocals globals builtins unbound')
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ def check_raises_type_error(self, message):
self.assertEqual(str(cm.exception), message)

def test_missing_arguments(self):
msg = "A.method_two_args() missing 1 required positional argument: 'y'"
msg = "A.method_two_args() missing 1 required argument: 'y'"
with self.check_raises_type_error(msg):
A().method_two_args("x")

Expand Down
18 changes: 9 additions & 9 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ class C:

with self.assertRaisesRegex(TypeError,
r"__init__\(\) missing 1 required "
"positional argument: 'x'"):
"argument: 'x'"):
C()

def test_field_default(self):
Expand Down Expand Up @@ -935,7 +935,7 @@ class C:
x: int=field(default=MISSING)
with self.assertRaisesRegex(TypeError,
r'__init__\(\) missing 1 required '
'positional argument'):
'argument'):
C()
self.assertNotIn('x', C.__dict__)

Expand All @@ -944,7 +944,7 @@ class D:
x: int
with self.assertRaisesRegex(TypeError,
r'__init__\(\) missing 1 required '
'positional argument'):
'argument'):
D()
self.assertNotIn('x', D.__dict__)

Expand All @@ -957,7 +957,7 @@ class C:
x: int=field(default_factory=MISSING)
with self.assertRaisesRegex(TypeError,
r'__init__\(\) missing 1 required '
'positional argument'):
'argument'):
C()
self.assertNotIn('x', C.__dict__)

Expand All @@ -966,7 +966,7 @@ class D:
x: int=field(default=MISSING, default_factory=MISSING)
with self.assertRaisesRegex(TypeError,
r'__init__\(\) missing 1 required '
'positional argument'):
'argument'):
D()
self.assertNotIn('x', D.__dict__)

Expand Down Expand Up @@ -3260,7 +3260,7 @@ class C:
# also have a default value (of type
# types.MemberDescriptorType).
with self.assertRaisesRegex(TypeError,
r"__init__\(\) missing 1 required positional argument: 'x'"):
r"__init__\(\) missing 1 required argument: 'x'"):
C()

# We can create an instance, and assign to x.
Expand Down Expand Up @@ -3784,7 +3784,7 @@ def __init_subclass__(cls, arg):

with self.assertRaisesRegex(
TypeError,
"missing 1 required positional argument: 'arg'",
"missing 1 required argument: 'arg'",
):
@dataclass(slots=True)
class WithWrongSuper(WrongSuper, arg=1):
Expand Down Expand Up @@ -4018,7 +4018,7 @@ def __set__(self, instance: Any, value: int) -> None:
class C:
i: D = D()

with self.assertRaisesRegex(TypeError, 'missing 1 required positional argument'):
with self.assertRaisesRegex(TypeError, 'missing 1 required argument'):
c = C()

class TestStringAnnotations(unittest.TestCase):
Expand Down Expand Up @@ -4227,7 +4227,7 @@ class Base2:
C = make_dataclass('C',
[('y', int)],
bases=(Base1, Base2))
with self.assertRaisesRegex(TypeError, 'required positional'):
with self.assertRaisesRegex(TypeError, 'required argument'):
c = C(2)
c = C(1, 2)
self.assertIsInstance(c, C)
Expand Down
16 changes: 8 additions & 8 deletions Lib/test/test_extcall.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,17 @@
>>> g()
Traceback (most recent call last):
...
TypeError: g() missing 1 required positional argument: 'x'
TypeError: g() missing 1 required argument: 'x'

>>> g(*())
Traceback (most recent call last):
...
TypeError: g() missing 1 required positional argument: 'x'
TypeError: g() missing 1 required argument: 'x'

>>> g(*(), **{})
Traceback (most recent call last):
...
TypeError: g() missing 1 required positional argument: 'x'
TypeError: g() missing 1 required argument: 'x'

>>> g(1)
1 () {}
Expand Down Expand Up @@ -505,27 +505,27 @@
>>> f()
Traceback (most recent call last):
...
TypeError: f() missing 1 required positional argument: 'a'
TypeError: f() missing 1 required argument: 'a'
>>> def f(a, b): pass
>>> f()
Traceback (most recent call last):
...
TypeError: f() missing 2 required positional arguments: 'a' and 'b'
TypeError: f() missing 2 required arguments: 'a' and 'b'
>>> def f(a, b, c): pass
>>> f()
Traceback (most recent call last):
...
TypeError: f() missing 3 required positional arguments: 'a', 'b', and 'c'
TypeError: f() missing 3 required 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'
TypeError: f() missing 5 required 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'
TypeError: f() missing 1 required argument: 'a'

Same with keyword only args:

Expand Down
8 changes: 4 additions & 4 deletions Lib/test/test_positional_only_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def f(a, b, /):
def test_positional_only_and_arg_invalid_calls(self):
def f(a, b, /, c):
pass
with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'c'"):
with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required argument: 'c'"):
f(1, 2)
with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'b' and 'c'"):
f(1)
Expand Down Expand Up @@ -170,7 +170,7 @@ def f(a, b, /, c, *, d, e):
f(1, 2, 3, e=2)
with self.assertRaisesRegex(TypeError, r"missing 2 required keyword-only arguments: 'd' and 'e'"):
f(1, 2, 3)
with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required positional argument: 'c'"):
with self.assertRaisesRegex(TypeError, r"f\(\) missing 1 required argument: 'c'"):
f(1, 2)
with self.assertRaisesRegex(TypeError, r"f\(\) missing 2 required positional arguments: 'b' and 'c'"):
f(1)
Expand Down Expand Up @@ -278,7 +278,7 @@ def g(x2,/,y2):
return g

self.assertEqual(f(1,2)(3,4), 10)
with self.assertRaisesRegex(TypeError, r"g\(\) missing 1 required positional argument: 'y2'"):
with self.assertRaisesRegex(TypeError, r"g\(\) missing 1 required argument: 'y2'"):
f(1,2)(3)
with self.assertRaisesRegex(TypeError, r"g\(\) takes 2 positional arguments but 3 were given"):
f(1,2)(3,4,5)
Expand All @@ -296,7 +296,7 @@ def g(x2,/,y2):
return g

self.assertEqual(f(1,2)(3,4), 10)
with self.assertRaisesRegex(TypeError, r"g\(\) missing 1 required positional argument: 'y2'"):
with self.assertRaisesRegex(TypeError, r"g\(\) missing 1 required argument: 'y2'"):
f(1,2)(3)
with self.assertRaisesRegex(TypeError, r"g\(\) takes 2 positional arguments but 3 were given"):
f(1,2)(3,4,5)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix an error message that incorrectly specified a missing argument as
"positional" in function calls.
49 changes: 41 additions & 8 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1322,13 +1322,22 @@ format_missing(PyThreadState *tstate, const char *kind,
}
if (name_str == NULL)
return;
_PyErr_Format(tstate, PyExc_TypeError,
"%U() missing %i required %s argument%s: %U",
qualname,
len,
kind,
len == 1 ? "" : "s",
name_str);
if (kind[0] == '\0') {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() missing %i required argument%s: %U",
qualname,
len,
len == 1 ? "" : "s",
name_str);
} else {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() missing %i required %s argument%s: %U",
qualname,
len,
kind,
len == 1 ? "" : "s",
name_str);
}
Py_DECREF(name_str);
}

Expand All @@ -1340,9 +1349,33 @@ missing_arguments(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t i, j = 0;
Py_ssize_t start, end;
int positional = (defcount != -1);
const char *kind = positional ? "positional" : "keyword-only";
const char *kind = "";
PyObject *missing_names;

if (positional) {
int has_posonly_missing = 0;

start = 0;
end = co->co_argcount - defcount;

for (i = start; i < end; i++) {
if (PyStackRef_IsNull(localsplus[i])) {
if (i < co->co_posonlyargcount) {
has_posonly_missing = 1;
break;
}
}
}

if (has_posonly_missing) {
kind = "positional";
} else {
kind = "";
}
} else {
kind = "keyword-only";
}

/* Compute the names of the arguments that are missing. */
missing_names = PyList_New(missing);
if (missing_names == NULL)
Expand Down
Loading