pythonintegerlimitboundarybounded-types

# How can I create a bounded int in Python?

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?

Solution

• Try this. It should work both for `int`s and `float`s:

``````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 `float`s 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`.