Be careful when naming fields in your Python exceptions
tl;dr: Python exceptions reserve the field "args" for internal magic. Don't name your field "args".
The Problem
Here’s a Python dataclass:
1
2
3
4
@dataclass
class InvalidCallError:
function: str
args: list
Nothing unusual in sight. We want it to be a real, raisable exception, so it needs to inherit Exception
(technically, BaseException
, but anyway):
1
class InvalidCallError(Exception):
Now, let’s try to raise such an exception:
1
raise InvalidCallError("foo", [1, 2, 3])
Since this is a dataclass, we would expect it to display something along the lines of InvalidCallError("foo", [1, 2, 3])
, but instead, we get this:
1
2
3
4
Traceback (most recent call last):
File "/app/output.s", line 8, in <module>
raise InvalidCallError("foo", [1, 2, 3])
InvalidCallError: (1, 2, 3)
foo
was lost in the process! What happened?
Let’s look at a variant:
1
2
3
4
5
6
7
8
9
@dataclass
class ArgList:
args: list
kwargs: dict
@dataclass
class InvalidCallError(Exception):
function: str
args: ArgList
Let’s throw it:
1
raise InvalidCallError("foo", ArgList([1, 2, 3], {"a": 4, "b": 5}))
Now it’s even more cryptic:
1
2
3
4
5
6
Traceback (most recent call last):
File "/app/output.s", line 13, in <module>
raise InvalidCallError("foo", ArgList([1, 2, 3], {"a": 4, "b": 5}))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 4, in __init__
TypeError: 'ArgList' object is not iterable
Something’s being thrown in the file… <string>
? At line 4? In __init__
? What __init__
?
If you try to debug this, you won’t even be able to see the frame where the exception was thrown.
What’s happening here?
BaseException
is a special class in Python, with the bulk of its methods implemented in C. The exact code we’re looking for is its constructor. When a BaseException
object is constructed, the arguments passed to the constructor are stored in the args
field. This is indeed what the docs say about it. However, what’s unclear is that this means you can’t easily have a custom exception type with a field named args
.
The second traceback we’ve seen, with the is not iterable
error, is simply due to some code somewhere assuming the exception has an args
field containing the list of its arguments.
It took me some time to track down the exact origin of the bug, but I couldn’t find anything online that warns people not to use the name args
for a field in a custom exception.