如何创建一个扩展``int``基类的类,该类具有输入验证?
如何创建一个扩展``int``基类的类,该类具有输入验证?
我想创建一个扩展了int
基类的类,使得对象本身是一个整数(即您可以直接设置和读取它),但也具有输入验证 - 例如,仅允许给定范围。从我所研究的来看,当您扩展常规基类时,__init__
方法不会被调用,因此我不确定该如何处理这个问题。我也不清楚如何访问对象的值(即分配给它的实际整数)或从类内修改该值。我看到了扩展任何基类(字符串、浮点数、元组、列表等)相同的问题。如果我可以使用__init__
它会是这样的:
class MyInt(int): def __init__(self, value): if value < 0 or value > 100: raise ValueError('MyInt objects must be in range 0-100, value was {}'.format(value)) self = value
我如何验证传入我的扩展整数类的新值?
在研究MarkM建议的链接上的__new__
函数之后,该方法将执行我正在寻找的操作,但我也在质疑它是否是正确的方法。请参见下面的讨论和解决方案部分。
关于子类化基本不可变类型的讨论
但是,我还在质疑需要这样做的必要性,至少对于简单的输入验证检查而言。子类化不可变基类的好处在于,您可以获得所有不可变性的好处,例如内置的哈希,字符串表示等,这些都直接继承自基类。但是,通过向另一个类添加属性,添加设置函数并使其不可变,也可以获得相同的好处。
子类化的优势在于,如果您想要不可变对象,所有这些对象都具有相同的输入验证和/或可以应用于许多不同类/模块的其他方法,则可以不需要创建将它们转换为可变对象的单独类的额外复杂性,然后需要额外的属性或方法访问(即您可以从"MyInt"类创建一个"myint"对象,但需要一个"value"属性或类似的东西来访问基础int,例如myint.value
而不仅仅是myint
)。
解决方案/实现
所以,也许有更好的方法来做到这一点,但为了回答自己的问题,在这里是我编写的一个测试脚本,以至少使其工作。
请注意,int可以有多个参数,当将字符串解释为特定基数时,例如('1111',2),它将二进制字符串“1111”转换为十进制数15。也可以将基数作为kwarg输入,因此必须完整地传递* args和** kwargs到int __new__
函数。
此外,我最终先调用int,然后再进行验证,以便它可以首先将浮点数和字符串转换为int,然后再尝试验证。
请注意,由于MyInt是int的子类,因此必须返回int值-您不能在失败时返回"None"(尽管可以返回0或-1)。这促使我引发ValueError并在主脚本中处理错误。
class MyInt(int): def __new__(cls, *args, **kwargs): value = super().__new__(cls, *args, **kwargs) argstr = ', '.join([str(arg) for arg in args]) # Only for prototype test print('MyInt({}); Returned {}'.format(argstr, value)) # Only for prototype test if value < 0 or value > 100: raise ValueError(' ERROR!! Out of range! Must be 0-100, was {}'.format(value)) return value if __name__ == '__main__': myint = MyInt('1111', 2) # Test conversion from binary string, base 2 print('myint = {} (type: {})\n'.format(myint, type(myint))) for index, arg in enumerate([-99, -0.01, 0, 0.01, 0.5, 0.99, 1.5, 100, 100.1, '101']): try: a = MyInt(arg) except ValueError as ex: print(ex) a = None finally: print(' a : {} = {}'.format(type(a), a))
在这种情况下,您实际上不必覆盖 __new__
。创建对象后,将调用 __init__
,您可以检查是否在范围内。
class MyInt(int): def __init__(self, x, **kwargs): if self < 0 or self > 100: raise ValueError('MyInt objects must be in range 0-100, value was {}'.format(x))
您可以覆盖 __new__
在 MyInt(...)
返回新对象之前引发异常。
class MyInt(int): def __new__(cls, *args, **kwargs): x = super().__new__(cls, *args, **kwargs) # Let any value error propagate if x < 0 or x > 100: raise ValueError('MyInt objects must be in range 0-100, value was {}'.format(x)) return x
在调用 super().__new__
之前尝试验证参数可能是有用的,但严格来说,这不太适用于其他类。