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: Confusing error message when None used in expressions, eg. "'NoneType' object has no attribute 'foo'"
Type: enhancement Stage: test needed
Components: Interpreter Core Versions: Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: gward Nosy List: brett.cannon, gward, levkivskyi, r.david.murray, serhiy.storchaka, terry.reedy
Priority: normal Keywords:

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 "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
>>> 1 + a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
>>> len(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'NoneType' has no len()
>>> a < 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
AttributeError: attempt to access attribute 'foo' of None, but None has no attributes
>>> a.foo = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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 <module>
    NotImplemented.foo
AttributeError: 'NotImplementedType' object has no attribute 'foo'
>>> Ellipsis.foo
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    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:39adminsetgithub: 72888
2016-12-23 22:48:41levkivskyisetnosy: + levkivskyi
2016-11-22 02:10:44gwardsetmessages: + msg281429
2016-11-18 19:42:22terry.reedysetversions: + Python 3.7, - Python 3.6
nosy: + terry.reedy

messages: + msg281160

stage: test needed
2016-11-15 20:58:52serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg280893
2016-11-15 20:43:19r.david.murraysetnosy: + r.david.murray
messages: + msg280892
2016-11-15 20:05:22gwardsetnosy: + brett.cannon
messages: + msg280885
2016-11-15 20:04:43gwardcreate