[Python-ideas] Should decimal.InvalidOperation subclass ValueError?

Nick Coghlan ncoghlan at gmail.com
Tue May 24 20:26:45 EDT 2016


On 25 May 2016 at 02:56, Guido van Rossum <gvanrossum at gmail.com> wrote:
> If it's the right thing to do we should do it, and consider how to do
> it without breaking too much code too soon (or without clear
> indication of failure). I don't think a PEP is needed unless there's a
> lot of discussion about alternatives or disagreement.

Recapping the thread, the specific problem Steven raised was that
catching ValueError will handle "float('not-a-valid-float')", but not
"Decimal('not-a-valid-float')", as the latter raises
decimal.InvalidOperation, which inherits from ArithmeticError rather
than ValueError.

There are 3 possible ways of changing that:

- make ArithmeticError a subclass of ValueError
- make decimal.InvalidOperation inherit from both ArithmeticError & ValueError
- raise a subclass of InvalidOperation that also inherits from
ValueError for decimal string conversions

The last one is clearly the lowest risk and lowest impact way to allow
ValueError to be used to catch decimal string conversion problems, so
in the absence of any other considerations, I'd just say "Let's do
that".

However, what came up in the course of the discussion is that those of
us participating in the thread don't actually know the original
rationale for ArithmeticError being entirely distinct from ValueError,
rather than being a subclass of it, which means there are other cases
where folks may be expecting ValueError to catch all conversion
errors, but it may in fact be missing some. As a toy example:

>>> def to_float(x):
...     try:
...         return float(x)
...     except ValueError:
...         return float("nan")
...
>>> to_float("not-a-float")
nan
>>> to_float(10**1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_float
OverflowError: int too large to convert to float
>>> from fractions import Fraction
>>> class LazyFraction:
...     def __float__(self):
...         return float(Fraction(1, 0))
...
>>> to_float(LazyFraction())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_float
  File "<stdin>", line 3, in __float__
  File "/usr/lib64/python3.5/fractions.py", line 186, in __new__
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(1, 0)

Before this thread, I would have said that the above "to_float()"
function would reliably return either the converted value or NaN in
the absence of coding bugs in a __float__ method implementation, but
the OverflowError and ZeroDivisionError cases above show that I would
have been wrong about that, and a more comprehensive exception clause
would be "except (ValueError, ArithmeticError):".

If you rule out turning ArithmeticError into a ValueError subclass in
3.6+, then we can go back to looking specifically at the decimal
string conversion behaviour. However, if you thought the builtin
exception hierarchy change was an idea worth considering further, then
it would address decimal string conversions as a side effect (since
those are already an ArithmeticError subclass).

Regards,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list