How can I create a bounded int in Python?

2.6k Views Asked by At

I want to create a class that subclasses from int, but sets a lower bound and upper bound on what number it can be be.

For example, if the lower bound is 2, then a = MyClass(1) should raise an exception.

I'm struggling because int doesn't seem to have an __init__ function, so I'm not sure how to subclass from it, and my attempts are giving me errors.

How should I go about doing this?

2

There are 2 best solutions below

0
Pro Q On BEST ANSWER

Try this. It should work both for ints and floats:

def BoundedNumber(number_class):
    def BoundedNumberClassCreator(class_name, lower_bound, upper_bound):
        if upper_bound and lower_bound and upper_bound < lower_bound:
            raise ValueError(f"Upper bound {upper_bound} is lower than the lower bound {lower_bound}")

        def new(cls, number):
            if lower_bound and number < lower_bound:
                raise ValueError(f"{number} is below the lower bound of {lower_bound} for this class")

            if upper_bound and upper_bound < number:
                raise ValueError(f"{number} is above the upper bound of {upper_bound} for this class")

            return number_class(number)

        return type(class_name, (number_class,),
                    {"__new__": new,
                     "__doc__": f"Class that acts like `{number_class.__name__}` but has an inclusive lower bound of {lower_bound} and an inclusive upper bound of {upper_bound}",
                     "lower_bound": lower_bound,
                     "upper_bound": upper_bound})

    return BoundedNumberClassCreator

BoundedInt = BoundedNumber(int)
BoundedFloat = BoundedNumber(float)

if __name__ == "__main__":
    IntBetween50And150 = BoundedInt('IntBetween50And150', 50, 150)
    print(IntBetween50And150(100) == 100)  # True
    try:
        IntBetween50And150(200)
    except ValueError as e:
        print(f"Caught the ValueError: {e}")  # Caught the value error: 200 is above the upper bound of 150 for this class

    print(IntBetween50And150(50.5))  # 50
    print(IntBetween50And150.__doc__) # Class that acts like `int` but has an inclusive lower bound of 50 and an inclusive upper bound of 150

The hard part with subclassing from int is that it doesn't have an __init__ function. Instead, you have to use the __new__ function.

The BoundedNumber class takes care of this, defining a __new__ function that both runs the int (or float) __new__ function by calling int (or float), but also runs its own checks on the bounds before doing so.

Since we want to dynamically create a new class, we're going to have to use the type function. This will allow us to create a new class with whatever bounds we want during runtime.

Technically, to answer your question, you only need the BoundedNumberClassCreator with int put in everywhere that the number_class is used, but since it works for floats as well, I figured I'd encapsulate it to reduce duplicate code.

One odd thing about this solution if if you ZeroToOne = BoundedInt('ZeroToOne', 0, 1) and then create i = ZeroToOne(1.1) it will throw an error, even though int(1.1) is within the designated range. If you don't like this functionality, you can swap the order of the checks and the return inside of the new method of the BoundedNumberClassCreator.

1
ashish-ucsb On

This is right off the bat, not many errors are handled.

class boundedInt:

    def __init__(self, lower, upper):
        self.x = None
        if (lower <= upper):
            self.lower = lower
            self.upper = upper
        else:
            raise ValueError("Lower Bound must be lesser than Upper Bound".format())  

    def assign(self, x):
        if (x>= self.lower and x<=self.upper):
            self.x = x
            return self.x
        else:
            raise ValueError("Not in bounds")

myInt = boundedInt(15,20) # create object myInt
f = myInt.assign(17) # assigns value to a variable