我试图理解如何正确地将int
子类化。一个目标是定义在特定二进制文件格式的结构中使用的类型。例如,一个无符号的16位整数。我定义了一个类,如下所示,它似乎实现了我的期望:
class uint16(int):
def __new__(cls, val):
if (val < 0 or val > 0xffff):
raise ValueError("uint16 must be in the range %d to %d" % (0, 0xffff))
return super(cls, cls).__new__(cls, val)
现在,我对没有参数的super
与(类型,对象)与(类型,类型)的使用不是非常清楚。我使用了super(cls, cls)
,正如我在类似场景中看到的那样
现在,C使创建有效地作为现有类型别名的类型变得很容易。比如说,
typedef unsigned int UINT;
别名可能有助于澄清类型的预期用途。不管你是否同意,二进制格式的描述有时可以做到这一点,如果是这样的话,那么为了清晰起见,在Python中复制这一点会很有帮助
因此,我尝试了以下方法:
class Offset16(uint16):
def __new__(cls, val):
return super(cls, cls).__new__(cls, val)
我本可以将Offset16
作为int
的子类,但是我想重复验证(更多重复的代码)。通过对uint16
进行子分类,我避免了重复的代码
但当我试图构造Offset16对象时,我得到一个递归错误:
>>> x = Offset16(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __new__
File "<stdin>", line 5, in __new__
File "<stdin>", line 5, in __new__
File "<stdin>", line 5, in __new__
[Previous line repeated 987 more times]
File "<stdin>", line 3, in __new__
RecursionError: maximum recursion depth exceeded in comparison
>>>
由于调用堆栈只重复了第5行(而不是交替的第3/5行),因此uint16.__new__
中的行被重新输入
然后我尝试用不同的方式修改Offset16.__new__
,将args改为super
,但大多数都不起作用。但最后一次尝试是:
class Offset16(uint16):
def __new__(cls, val):
return super(uint16, cls).__new__(cls, val)
这似乎有效:
>>> x = Offset16(42)
>>> x
42
>>> type(x)
<class '__main__.Offset16'>
为什么不同
后一种方法似乎破坏了super
的部分目的:避免对基类的引用以使其更易于维护。有没有一种方法可以使这项工作不需要在__new__
实现中引用uint16
呢
最好的方法是什么
评论提供的信息有助于回答为什么会有差异和最好的方法是什么
第一:为什么不同
在
uint16
和Offset16
的原始定义中,__new__
方法使用super(cls,cls)
。正如@juanpa.arrivillaga指出的,当调用Offset16.__new__
时,它会导致uint16.__new__
递归地调用自己。通过让Offset16.__new__
使用super(uint16,cls)
,它改变了uint16.__new__
内部的行为一些额外的解释可能有助于理解:
传递到
Offset16.__new__
的cls
参数是Offset16
类本身。因此,当方法的实现引用cls
时,即引用Offset16
。所以在这种情况下等于
现在,我们可能认为
super
返回基类,但是当提供参数时,它的语义更加微妙:super
正在解析对方法的引用,参数会影响解析的方式。如果没有提供参数,super().__new__
是立即超类中的方法。如果提供了参数,则会影响搜索。特别是对于super(type1, type2)
,将搜索type2
的MRO(方法解析顺序)以查找type1
的出现,并且将使用该序列中type1后面的类(这在documentation of ^{} 中有解释,不过措辞可能更清楚。)
Offset16
的MRO是(Offset16,uint16,int,object)。所以决心
以这种方式调用
uint16.__new__
时,传递给它的类参数是Ofset16
,而不是uint16
。因此,当它的实现这将再次决定
这就是我们最终得到无限循环的原因
但是在
Offset16
的变化定义中最后一行相当于
根据上面提到的
Offset16
的MRO和super
的语义,解析为这就解释了为什么改变定义会导致不同的行为
第二:最好的方法是什么
评论中提供了可能适合不同情况的不同备选方案
@juanpa.arrivillaga建议(假设Python3)只使用
super()
而不使用参数。对于问题中采取的方法来说,这是有意义的。将参数传递给super
的原因是为了操纵MRO搜索。在这个简单的类层次结构中,这是不需要的@Jason Yang建议直接引用特定的超类,而不是使用
super
。例如:对于这种简单的情况,这是非常好的。但对于具有更复杂类关系的其他场景,它可能不是最好的。例如,请注意
uint16
在上面的示例中是重复的。如果子类有几个方法包装(而不是替换)了超类方法,那么会有许多重复引用,对类层次结构进行更改将导致难以分析的错误。避免这些问题是使用super
的预期好处之一最后,@Adam.Er8建议只使用
事实上,这很简单。一个警告是
Offset16
实际上只不过是uint16
的别名;这不是一门单独的课。例如:因此,只要应用程序中不需要实际的类型区分,这就可以了
相关问题 更多 >
编程相关推荐