I just built 3.11.2 for myself (the day before 3.11.3 comes out, naturally) and have been playing around with the new exception tracebacks. The concept is excellent, and it’s a great start towards something I’ve hoped to see implemented for a long time.
However, there are some more things I would have hoped for that I’m not seeing:
Inconsistent treatment of operands
If I generate a TypeError
from invalid subscripting, Python will highlight the subscript and the subscripted-thing differently:
>>> import bad
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/bad.py", line 1, in <module>
[None][0][1]
~~~~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable
Similarly if I generate an IndexError
:
>>> import bad
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/bad.py", line 1, in <module>
'test'[:0][0]
~~~~~~~~~~^^^
IndexError: string index out of range
However, curiously, attribute access doesn’t seem to be able to do this:
>>> import bad
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/bad.py", line 3, in <module>
object().attribute = None
^^^^^^^^^^^^^^^^^^
AttributeError: 'object' object has no attribute 'attribute'
Meanwhile, using more “symmetrical” binary operators like +
:
>>> import bad
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/bad.py", line 1, in <module>
0 + '1'
~~^~~~~
TypeError: unsupported operand type(s) for +: 'int' and 'str'
I actually prefer this style: there’s one form of highlighting for the operator, and another used for both operands. But the subscripts work differently: there’s one form of highlighting for the left-hand side, and a different form for both the operator and the “right”-hand side. I guess this is a consequence of the operator being made of up multiple, non-adjacent symbols (the two square brackets), but I honestly don’t think I like it.
Files vs REPL
None of this seems to work in the REPL, even if you define a function to wrap the dirty work:
>>> def bad():
... 'test'[:0][0]
...
>>> bad()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in bad
IndexError: string index out of range
It used to make sense to suppress all the code in REPL tracebacks, since clearly the error is referring to the code that you just typed, and it’s right there. However, now that there’s functionality to highlight parts of the line, suppressing the code misses the opportunity to do that highlighting and explain which string index was out of range.
The error messages
Since Python can now see what source code corresponds to the operands of the failed operation, why not incorporate that information into the message? Better yet, show the values as well. And while we’re at it, AttributeError
ought to be able to distinguish between gets and sets.
Examples could look like:
[None][0][1]
~~~~~~~~~^~^
TypeError: cannot subscript None (the result of `[None][0]`, of type 'NoneType')
'test'[:0][0]
~~~~~~~~~~^~^
IndexError: `0` is an invalid index for '' (the result of `'test'[:0]`)
object().attribute = None
~~~~~~~~^~~~~~~~~~
AttributeError: not allowed to set attribute `attribute` of an 'object' instance (the result of `object()`)
(There seem to be existing conventions of showing the repr
of values directly in error messages, and putting type names inside single quotes; I’ve followed that, but I’m also here proposing to use backticks in error messages to surround excerpts from the code.)
Or perhaps:
[None][0][1]
~~~~~~~~~^~^
TypeError: `[None][0]` is a 'NoneType', so it cannot be subscripted
'test'[:0][0]
~~~~~~~~~~^~^
IndexError: `'test'[:0]` has length 0, so `0` is an invalid index
object().attribute = None
~~~~~~~~^~~~~~~~~~
AttributeError: `object()` is an 'object' instance, so setting its `attribute` attribute is not allowed
Finally: maybe we could special-case to recognize None, True and False, and describe them as such, rather than as 'NoneType' object
s or 'bool' object
s.