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)

msg280884 - (view)

Author: Greg Ward (gward) (Python committer)

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.

msg280885 - (view)

Author: Greg Ward (gward) (Python committer)

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!

msg280892 - (view)

Author: R. David Murray (r.david.murray) * (Python committer)

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' :)

msg280893 - (view)

Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer)

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.

msg281160 - (view)

Author: Terry J. Reedy (terry.reedy) * (Python committer)

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.

msg281429 - (view)

Author: Greg Ward (gward) (Python committer)

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