ZeroDivisionError as a (virtual) subclass of ValueError

round(float("nan")) raises a ValueError, because although round takes the float type, it rejects that particular float value.
a//0 raises an exception which is not a ValueError and will not be caught by except ValueError:, despite being, in my view, in a similar situation as just above.

Why isn’t ZeroDivisionError a subclass of ValueError, when it’s clearly an error due to passing one particular value which is zero ?

The Exception page says that “it’s recommended to avoid subclassing multiple exception types altogether”, so maybe it’s not that easy, or a good idea implementation-wise, to make it a real subclass of both ArithmeticError (which it currently is) and ValueError. But maybe that can be solved by adressing it in C directly ? Or by making it a virtual subclass, like the collections.abc ABCs do (without the massive metaclass hurdle that goes with it of course) ?

This is not a priority issue, I don’t have a particular code that suffers from it, so at worst it could just stay a documented peculiarity of the exception inheritance tree. But is there a feature argument why it shouldn’t be done ?

When is an ArithmeticError not a value error?

When its a program’s LogicError :slight_smile:

That’s a good question. I don’t think it currently happens in the stdlib, but a function that would take two values and return the int value of a//b, would be entitle to raise an ArithmeticError in the following case :

import math
u = math.ulp(.0)

flordiv(u, u) # returns 1
flordiv(4., 4.) # returns 1
flordiv(4., u) # raises an OverflowError, which is an ArithmeticError

In the latter case, I don’t think ValueError would be relevent, since flordiv can take 4. as the first value, and u as the second, it’s only when used at the same time that they cause an error.

Virtual subclassing will be of no use here if it’s done in the same way as the collections.abc module, as exception handling doesn’t respect virtual subclasses, only “true” subclasses:

Python 3.11.2 (tags/v3.11.2:878ead1, Feb  7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from abc import ABCMeta
>>> class MyException(Exception, metaclass=ABCMeta): pass
...
>>> class RealSubclass(MyException): pass
...
>>> @MyException.register
... class VirtualSubclass(Exception): pass
...
>>> issubclass(RealSubclass, MyException)
True
>>> issubclass(VirtualSubclass, MyException)
True
>>> try:
...     raise RealSubclass
... except MyException:
...     print('Successfully caught!')
...
Successfully caught!
>>> try:
...     raise VirtualSubclass
... except MyException:
...     print('Successfully caught!')
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
VirtualSubclass
1 Like

shrug

Probably because division by zero errors can be handled differently from arbitrary value errors. Making ZeroDivisionError a subclass of ValueError risks shadowing the exception:


try:

    ...

except ValueError:

    # this would also catch ZeroDivisionError if it were a subclass

except ZeroDivisionError:

    # this would never run

But if they are distinct exceptions, not parent & child, then the order of except clauses doesn’t matter and there is no shadowing.

Also because not all ValueErrors are arithmetic errors:


>>> unicodedata.normalize('ABCD', 'hello world')

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

ValueError: invalid normalization form