Issue 27389: When a TypeError is raised due to invalid arguments to a method, it should use qualname to identify the class the method is in (original) (raw)
When a method is called with incorrect arguments (too many or too few, for instance), a TypeError is raised. The message in the TypeError generally of the form:
foo() takes 2 positional arguments but 3 were given
I think the message should include the class name along with the method name, so it would say SomeClass.foo
instead of just foo
. Since that is SomeClass.foo
's qualname, it should not be too hard to get the right name in most situations.
Here's an example showing how the current error messages can be ambiguous:
class A: def foo(self, x): pass
class B: def foo(self, x, y): # different method signature! pass
lst = [A(), B()]
for item in lst: item.foo(1) # raises TypeError: foo() missing 1 required positional argument: 'y'"
for item in lst: item.foo(1, 2) # raises "TypeError: foo() takes 2 positional arguments but 3 were given"
In neither loop is is clear which class's foo
method is causing the exception (nor does the traceback help, since it only shows the item.foo(...)
line). Of course, in this example it's easy to see the two classes have foo
methods with different signatures, but if there were hundreds of objects in the list and they were instances of dozens of different classes it would be rather more annoying to figure out which class has the incorrect method signature.
I've looked through the code and the two exceptions above come from the format_missing
and too_many_positional
functions in Python/ceval.c . It's not obvious how to patch them to use __qualname__
instead of __name__
, since they are taking the name from a code object, rather than a function object or bound method object (and code objects don't have an equivalent to __qualname__
, only co_name
which is related to __name__
).
Several other argument related TypeError exceptions are raised directly in _PyEval_EvalCodeWithName, which does have a qualname
parameter, though the function doesn't use it for much. It's also where the other functions described above get called from, so it could probably pass the qualname
along to them. Alas, it seems that in some common cases (such as calling a Python function with any kind of argument unpacking like *foo
or **foo
), the value of the qualname
parameter is actually Null, so it may not be of much help.
A few extra TypeErrors related to function calls are raised directly in the gigantic PyEval_EvalFrameEx
function. These seem to all use PyEval_GetFuncName
to get the name, so perhaps we could modify its behavior to return the method's __qualname__
rather than the __name__
. (I have no idea what backwards compatibility issues this might cause. Perhaps a new function that returns the qualname would be better.)