我想创建一个扩展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
如何验证进入扩展int类的新值?
在这种情况下,实际上不必重写__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__
之前,您可能需要尝试验证参数,但严格地说,这在其他类中效果不佳。
在研究了MarkM建议的链接中的__new__
函数后,这种方法可以满足我的需求,但我也在质疑它是否是正确的方法。请参阅下面的讨论和解决方案部分。
关于基不可变tyeps子类化的讨论
然而,我也在质疑这样做的必要性,至少对于简单的输入验证检查是这样。将不可变基类子类化的好处是,您可以获得不可变性的所有好处,如内置哈希、字符串表示等,这些好处只是直接从基类继承。但是,只需向另一个类添加一个属性,添加一个setter函数,并使其不可变,就可以获得同样的好处。
子类化的优点是,如果您想要具有相同输入验证和/或可应用于许多不同类/模块的附加方法的不可变对象,而不需要创建将它们转换为可变对象的单独类的额外复杂性,然后需要额外级别的属性或方法访问(即,从"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))
使用GetAttr使普通类对象返回包装的"Private"属性
这是我遇到的另一个答案,值得在这里输入。由于它被埋葬在一个答案的末尾,而这个答案并不是一个有点不同的问题的公认答案,我将其转发到这里。
我一直在为许多案例寻找这种特定的替代方案,事实上它就在这里!!当直接查询任何对象时,在设计类时,似乎应该有某种方式可以按照您的意愿进行回复。这就是这个答案的作用。
因此,例如,您可以使用一个普通类,但让类对象的查询返回一个int。这允许对象将int存储在一个单独的变量中,因此它是可变的,但通过不需要访问特定字段,只需直接查询对象(即myint = MyInt(1); print(myint)
请首先注意他的建议,以真正考虑为什么不能先对适当的类型进行子类化(即在此处使用其他答案之一)。
备选答案
这是从这个StackOverflow答案中复制的。这一切都归功于弗朗西斯·阿维拉。这是使用list
而不是int
作为子类,但想法相同:
- 看看Python中使用魔术方法的指南
- 如果您想要的是非常类似列表的内容,请说明为什么不将
list
子类化。如果子类化不合适,可以委托给包装列表实例:
class MyClass(object):
def __init__(self):
self._list = []
def __getattr__(self, name):
return getattr(self._list, name)
# __repr__ and __str__ methods are automatically created
# for every class, so if we want to delegate these we must
# do so explicitly
def __repr__(self):
return "MyClass(%s)" % repr(self._list)
def __str__(self):
return "MyClass(%s)" % str(self._list)
This will now act like a list without being a list (i.e., without subclassing `list`).
```sh
>>> c = MyClass()
>>> c.append(1)
>>> c
MyClass([1])