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

Guido van Rossum gvanrossum at gmail.com
Tue May 24 22:04:46 EDT 2016

Is there really something here that needs to be fixed? It's very
common for modules to define their own root exception class. And
usually people who care about these kinds of exceptions just catch
whatever exception is raised by the combination of argument they care
about (e.g. calling float() with a string argument). The Decimal
module already doesn't play all that much by the same rules as other
modules (e.g. it doesn't participate in the numeric tower) so people
trying to write algorithms independent from whether the numbers
they're processing are floats, fractions or Decimals are already in a
lot of pain, I imagine -- and the best solution is probably to just
stick with Decimal (numerical specialists would cringe at attempts to
write code without knowing how the arithmetic is done in detail

If there's anything in this area that bugs me it would be that I'd
worry that some obscure invalid input string to float() might not
raise ValueError but something else (e.g. OverflowError -- while I
cannot reproduce this, I've seen lots of code that catches that). But
in Decimal I would expect this all to be specified by the standard, so
I see no need to lose sleep there.

On Tue, May 24, 2016 at 5:26 PM, Nick Coghlan <ncoghlan at gmail.com> wrote:
> 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

--Guido van Rossum (python.org/~guido)

More information about the Python-ideas mailing list