Попробуй это. Должно работать как на int
с, так и на float
с:
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
Сложность с подклассами из int
заключается в том, что она не имеет функции __init__
. Вместо этого вы должны использовать функцию __new__
.
Класс BoundedNumber
заботится об этом, определяя функцию __new__
, которая одновременно выполняет функцию int
(или float
) __new__
, вызывая int
(или float
), но также перед этим выполняет свои собственные проверки границ.
Поскольку мы хотим динамически создать новый класс, нам нужно использовать функцию type
. Это позволит нам создать новый класс с любыми границами, которые мы хотим во время выполнения.
Технически, чтобы ответить на ваш вопрос, вам нужно только вставить BoundedNumberClassCreator
с int
везде, где используется number_class
, но, поскольку он работает и для float
s, я решил, что инкапсулирую это чтобы уменьшить дубликат кода.
Одна странная вещь в этом решении, если вы ZeroToOne = BoundedInt('ZeroToOne', 0, 1)
и затем создадите i = ZeroToOne(1.1)
, оно выдаст ошибку, даже если int(1.1)
находится в пределах указанного диапазона. Если вам не нравится эта функция, вы можете поменять местами порядок чеков и возврата в методе new
BoundedNumberClassCreator
.