Issue 28702: Confusing error message when None used in expressions, eg. "'NoneType' object has no attribute 'foo'" (original) (raw)
Created on 2016-11-15 20:04 by gward, last changed 2022-04-11 14:58 by admin.
Messages (6)
Author: Greg Ward (gward)
Date: 2016-11-15 20:04
Python's error message when you let None accidentally sneak into an expression where it doesn't belong could be better. The canonical example is attribute lookup:
a = None a.foo Traceback (most recent call last): File "", line 1, in AttributeError: 'NoneType' object has no attribute 'foo'
This assumes that the programmer knows there is only one object of type NoneType, and it is None. That's a lot to assume of a beginner, whether they are coming from another programming language ("null has a type? that's crazy talk!") or are completely new to programming ("null? none? nil? wat...??").
There are plenty of other places this use of NoneType in error messages comes up:
a + 1 Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'NoneType' and 'int' 1 + a Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for +: 'int' and 'NoneType' len(a) Traceback (most recent call last): File "", line 1, in TypeError: object of type 'NoneType' has no len() a < 1 Traceback (most recent call last): File "", line 1, in TypeError: '<' not supported between instances of 'NoneType' and 'int'
I think we can do better than this. For example, here is an proposed improvement to user experience for getting and setting attributes on None:
a.foo Traceback (most recent call last): File "", line 1, in AttributeError: attempt to access attribute 'foo' of None, but None has no attributes a.foo = 42 Traceback (most recent call last): File "", line 1, in AttributeError: attempt to set attribute 'foo' on None, but None is read-only
Let the bikeshedding commence. I have a working patch, but need to write tests. Will upload it here when that is done.
Author: Greg Ward (gward)
Date: 2016-11-15 20:05
Based on a brief conversation with Brett Cannon at PyCon Canada the other day. Thanks for the idea, Brett!
Author: R. David Murray (r.david.murray) *
Date: 2016-11-15 20:43
That presumably means adding special None support to all the places None can appear in a message, where now the code treats None like it does every other object. I'm not sure the added complexity is worth it, especially since NoneType would still creep in anywhere we'd forgotten to "fix". I'm not voting -1, but I'm dubious.
Maybe we should just make NoneType's name be 'None' :)
Author: Serhiy Storchaka (serhiy.storchaka) *
Date: 2016-11-15 20:58
Approximate counts.
C code: ob_type->tp_name 161 Py_TYPE(...)->tp_name 285
Python code: class.name 224 class.qualname 23 type(...).name 112 type(...).qualname 5
Is it worth changing about 800 places in CPython code? Not counting third-party code.
Author: Terry J. Reedy (terry.reedy) *
Date: 2016-11-18 19:42
I have doubts also.
The issue is the same for NotImplemented, though the occurrence is much rarer, and similar for Ellipsis.
NotImplemented.foo Traceback (most recent call last): File "<pyshell#19>", line 1, in NotImplemented.foo AttributeError: 'NotImplementedType' object has no attribute 'foo' Ellipsis.foo Traceback (most recent call last): File "<pyshell#20>", line 1, in Ellipsis.foo AttributeError: 'ellipsis' object has no attribute 'foo'
Replacing the type name with the object name works for this message, but not for the type errors. TypeError: unsupported operand type(s) for +: 'None' and 'int' is wrong.
Replacing 'NoneType' with 'None' in error messages will break code that does something like "if 'NoneType' in err.args[0]" in an exception message. The same replacement would have to be make in user code. Fortunately, it would continue to work with older versions.
Author: Greg Ward (gward)
Date: 2016-11-22 02:10
Is it worth changing about 800 places in CPython code? Not counting third-party code.
Definitely not. My aim is not to fix every possible reference to "instance of 'NoneType'", just the handful of cases that are most frequently encountered, especially if we think they are likely to be confusing to beginners. That's why I've only modified getting and setting attributes so far; I wanted to see what the cost/benefit is like.
Renaming 'NoneType' to 'None' sounds like a much easier approach, if it works. But then saying "instance of" + tp_name comes out weird. "Instance of NoneType" is confusing if technically accurate; "instance of None" is both confusing and technically inaccurate.
Hmmmm. Still mulling.
History
Date
User
Action
Args
2022-04-11 14:58:39
admin
set
github: 72888
2016-12-23 22:48:41
levkivskyi
set
nosy: + levkivskyi
2016-11-22 02:10:44
gward
set
messages: +
2016-11-18 19:42:22
terry.reedy
set
versions: + Python 3.7, - Python 3.6
nosy: + terry.reedy
messages: +
stage: test needed
2016-11-15 20:58:52
serhiy.storchaka
set
nosy: + serhiy.storchaka
messages: +
2016-11-15 20:43:19
r.david.murray
set
nosy: + r.david.murray
messages: +
2016-11-15 20:05:22
gward
set
nosy: + brett.cannon
messages: +
2016-11-15 20:04:43
gward
create