Post

Be careful when naming fields in your Python exceptions

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.

This post is licensed under CC BY 4.0 by the author.