This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Show qualified function name when giving arguments error
Type: enhancement Stage:
Components: Interpreter Core Versions: Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: cool-RR, steven.daprano
Priority: normal Keywords:

Created on 2020-01-04 18:52 by cool-RR, last changed 2022-04-11 14:59 by admin.

Messages (5)
msg359302 - (view) Author: Ram Rachum (cool-RR) * Date: 2020-01-04 18:52
I recently got this familiar error:

    builtins.TypeError: __init__() takes 1 positional argument but 2 were given

It was annoying that I didn't know which `__init__` method was under discussion. I wish that Python used the `__qualname__` of the function to show this error message (and maybe others?) so it'll show like this: 

    builtins.TypeError: FooBar.__init__() takes 1 positional argument but 2 were given

If I'm not mistaken, the implementation of this error is in getargs.c in the function vgetargs1_impl.
msg359310 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2020-01-04 22:41
> builtins.TypeError: __init__() takes 1 positional argument but 2 were given

How did you get the fully qualified exception? I just get "TypeError". (Also the error message is different in 3.8.)


> It was annoying that I didn't know which `__init__` method was under discussion.

Couldn't you tell from the stacktrace showing the failed line of code?

I don't know about this. While I can certainly see that there are circumstances where it might be useful (e.g. you're running a .pyc file and there's no source line available), I think that in the majority of the cases showing fully-qualified names would be more annoying than useful, and certainly more intimidating to beginners.
msg359385 - (view) Author: Ram Rachum (cool-RR) * Date: 2020-01-05 22:32
Here is a short IPython session:

In [1]: class Foo:                                                         
   ...:     def __init__(self, x):                                         
   ...:         pass                                                       
   ...:                                                                    
                                                                           
In [2]: Foo(7, 8)                                                          
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-efa33418c6bb> in <module>                                 
----> 1 Foo(7, 8)                                                          
                                                                           
TypeError: __init__() takes 2 positional arguments but 3 were given        

As you can see, it's pretty simple to get this exception text, so I'm not sure why you didn't get that text. This is on Python 3.8.1.

Regarding you saying it's more annoying than useful: Especially for methods such as `__init__`, it's often difficult to understand which class is being instantiated, especially in a complex codebase. It happened to me last week at work, and even with a debugger and being an experienced Python developer, it took me a few minutes to figure out which `__init__` method was being called.
msg359413 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2020-01-06 09:50
On Sun, Jan 05, 2020 at 10:32:26PM +0000, Ram Rachum wrote:
[...]
> TypeError: __init__() takes 2 positional arguments but 3 were given        
> 
> As you can see, it's pretty simple to get this exception text, so I'm not sure why you didn't get that text. 

But I *did* get that text. I'm asking how you got the *different* text 
which you gave in your initial request.

Firstly, you stated that you got the fully-qualified exception type:

    builtins.TypeError

rather than just TypeError, which was my question. How did you get the 
fully-qualified exception class?

Secondly you quoted the error message:

    __init__() takes 1 positional argument but 2 were given

which is what I get in 3.5 using `def __init__(self)`, but in 3.8 it 
gives a different message "MyClass() takes no arguments".

I'm not worried about the error message discrepency, since that's 
irrelevant to your enhancement request, and easily explained if you were 
quoting from an older version of Python. But I don't know how you got 
the `builtins.TypeError` fully-qualified part. That is the part I'm 
asking about.

> Regarding you saying it's more annoying than useful: Especially for 
> methods such as `__init__`, it's often difficult to understand which 
> class is being instantiated, especially in a complex codebase.

I agree with you that there are occasions where this could be difficult. 
Apart from the "no source code" case, I can see that might be the case 
if the failing line is a complex expression like this:

    do_things(Spam(arg)[Eggs(None)] + Cheese(Aardvark(thingy)))

and you don't know which of Spam, Eggs, Cheese or Aardvark has failed. 
Or in an expression like this:

    lookup_class['some key'](arg)

where the source is only an indirect reference to the class you want.

But in my experience, the great bulk of calls to classes (apart from 
simple "atomic" values like int, float, str, etc) stand alone:

    obj = MyClass(arg)

and there is no difficulty in telling which class has the problem.

In my experience, I would say > 90% easy cases and < 10% tricky cases. 
(YMMV.)

So in my opinion, and I recognise that you may disagree, this is only of 
benefit in a minority of cases. For the majority of cases, it will only 
be additional noise in the message that adds nothing that wasn't already 
obvious from the source line.

And in particular, beginners are often intimidated by error messages, 
and the longer the error message, the more they are intimidated. In that 
case, `TypeError: __init__ ...` is shorter and therefore less scary than 
`builtins.TypeError: package.some_module.MyClass.__init__ ...`.

So I'm not disputing that this will be useful *sometimes*. I'm just 
trying to weigh up the cons of longer, more complex error messages 
with redundant information versus the occasional cases where the 
information is helpful.
msg359430 - (view) Author: Ram Rachum (cool-RR) * Date: 2020-01-06 14:56
> But I *did* get that text. I'm asking how you got the *different* text 
> which you gave in your initial request.

Oh, right. Looking back, turns out Wing IDE did that for me. Didn't notice, sorry.

> So in my opinion, and I recognise that you may disagree, this is only of 
> benefit in a minority of cases. For the majority of cases, it will only 
> be additional noise in the message that adds nothing that wasn't already 
> obvious from the source line.

I understand your perspective.

One correction though regarding adding noise to the error. You gave this example:

    builtins.TypeError: package.some_module.MyClass.__init__ takes 1 positional argument but 2 were given
    
While actually it'll be:

    TypeError: MyClass.__init__ takes 1 positional argument but 2 were given
    
Since __qualname__ only includes the class, not its module. So it's quite a bit shorter.
History
Date User Action Args
2022-04-11 14:59:24adminsetgithub: 83393
2020-01-06 14:56:41cool-RRsetmessages: + msg359430
2020-01-06 09:51:00steven.dapranosetmessages: + msg359413
2020-01-05 22:32:26cool-RRsetmessages: + msg359385
2020-01-04 22:41:13steven.dapranosetnosy: + steven.daprano
messages: + msg359310
2020-01-04 18:52:25cool-RRcreate