Search code examples
pythonpython-3.xoverflowexception

Catching OverflowError


In Python 3 I have a class representing a range [x,y] of values and computing the length of such range.

If the length is too large, I'm not sure how to catch the OverflowError exception inside the class itself. It is raised only in the outer code using a class instance...

class ValRange:
    val_min = None
    val_max = None
    range_len = 0

    def __init__(self, val_min, val_max):
        self.val_min = val_min
        self.val_max = val_max

    def __len__(self):
        if (self.val_min is not None and
             self.val_max is not None):
            length = 0
            try:
                length = int(self.val_max) - int(self.val_min) + 1
            except OverflowError as e:
                # Somehow no exception is caught here...
                print('OverflowError...')
                length = 10**5  # arbitrarily large number
            except Exception as e:
                print(e)
            if length > 0:
                self.range_len = length
        return self.range_len


import traceback
import uuid
x = uuid.UUID('00000000-cb9d-4a99-994d-53a499f260b3')
y = uuid.UUID('ea205f99-0564-4aa0-84c3-1b99fcd679fd')
r = ValRange(x, y)
try:
    print(len(r))
except:
    # The exception is caught in this outer code and not in the class itself. Why?
    print(traceback.format_exc())

# The following, which is equivalent to the operations in the 
# code above, will work. 
a = int(y) - int(x) + 1
print(a)

This is what happens upon execution:

Traceback (most recent call last):
  File "/home/rira/bugs/overflow.py", line 35, in <module>
    print(len(r))
OverflowError: cannot fit 'int' into an index-sized integer

311207443402617699746040548788952897867

Solution

  • That's because the OverflowError doesn't occur within your magic __len__() method - Python is perfectly capable of handling much bigger integers than that - but in CPython len() itself is implemented as PyObject_Size() which returns a Py_ssize_t, which is limited to 2^31-1 (32-bit) or 2^63-1 (64-bit) and thus the overflow occurs when your __len__() result is coerced to it.

    You can do a pre-check before returning the result to make sure you capture the overflow before it even occurs, something like:

    def __len__(self):
        if (self.val_min is not None and self.val_max is not None):
            length = int(self.val_max) - int(self.val_min) + 1
            if length > sys.maxsize:
                print('OverflowError...')
                length = 10**5  # arbitrarily large number
            self.range_len = length
        return self.range_len