如何创建一个扩展``int``基类的类,该类具有输入验证?

10 浏览
0 Comments

如何创建一个扩展``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

我如何验证传入我的扩展整数类的新值?

admin 更改状态以发布 2023年5月21日
0
0 Comments

在研究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))

0
0 Comments

在这种情况下,您实际上不必覆盖 __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__ 之前尝试验证参数可能是有用的,但严格来说,这不太适用于其他类。

0