python: int 子类的子类
python: subclass of subclass of int
我正在尝试了解如何正确地 subclass int
。一个目标是在某种二进制文件格式中定义结构中使用的类型。例如,一个无符号的 16 位整数。我定义了一个 class 如下,这似乎符合我的预期:
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
的使用(不带参数)与 (type, object) 与 (type, type)。我使用了 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
的子 class,但我想重复验证(更多重复代码)。通过 sub-classing 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__
,将参数更改为 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
的部分目的:避免引用基础 class 以使其更易于维护。有没有一种方法可以使这项工作不需要在 __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
class 本身。所以,当方法的实现引用了cls
,也就是引用了Offset16
。所以,
return super(cls, cls).__new__(cls, val)
在这种情况下等同于
return super(Offset16, Offset16).__new__(Offset16, val)
现在我们可能认为 super
返回基数 class,但它在提供参数时的语义更加微妙:super
正在解析对方法的引用和争论会影响该决议的发生方式。如果没有提供参数,super().__new__
是直接 superclass 中的方法。提供参数时,会影响搜索。特别是对于 super(type1, type2)
,将搜索 type2
的 MRO(方法解析顺序)以查找 type1
的出现,以及 type1[ 之后的 class =119=] 将使用该序列。
(这在 documentation of super
中有解释,尽管措辞可能更清楚。)
Offset16
的 MRO 是 (Offset16, uint16, int, object)。因此
return super(Offset16, Offset16).__new__(Offset16, val)
解析为
return uint16.__new__(Offset16, val)
当以这种方式调用uint16.__new__
时,传递给它的class参数是Ofset16
,而不是uint16
。结果,当它的实现有
return super(cls, cls).__new__(cls, val)
将再次解析为
return uint16.__new__(Offset16, val)
这就是我们以无限循环结束的原因。
但在 Offset16
的更改定义中,
class Offset16(uint16):
def __new__(cls, val):
return super(uint16, cls).__new__(cls, val)
最后一行相当于
return super(uint16, Offset16).__new__(Offset16, val)
并且根据 Offset16
的 MRO 和上面提到的 super
的语义,解析为
return int.__new__(Offset16, val)
这解释了为什么更改后的定义会导致不同的行为。
第二:最好的方法是什么?
评论中提供了可能适合不同情况的不同备选方案。
@juanpa.arrivillaga 建议(假设 Python3)简单地使用 super()
而不带参数。对于问题中采用的方法,这是有道理的。将参数传递给 super
的原因是为了操纵 MRO 搜索。在这个简单的 class 层次结构中,不需要这样做。
@Jason Yang 建议直接引用特定的 superclass 而不是使用 super
。例如:
class Offset16(uint16):
def __new__(cls, val):
return uint16.__new__(cls, val)
对于这种简单的情况,这完全没问题。但对于具有更复杂 class 关系的其他场景,它可能不是最好的。请注意,例如,uint16
在上面重复了。如果 subclass 有几个包装(而不是替换)superclass 方法的方法,就会有很多重复引用,并且对 class 层次结构进行更改会导致硬-分析错误。避免此类问题是使用 super
的预期好处之一。
最后,@Adam.Er8建议简单地使用
Offset16 = uint16
这确实很简单。需要注意的是 Offset16
实际上只是 uint16
的一个别名;它不是单独的 class。例如:
>>> Offset16 = uint16
>>> x = Offset16(24)
>>> type(x)
<class 'uint16'>
所以,这可能没问题只要应用程序永远不需要有实际的类型区分。
我正在尝试了解如何正确地 subclass int
。一个目标是在某种二进制文件格式中定义结构中使用的类型。例如,一个无符号的 16 位整数。我定义了一个 class 如下,这似乎符合我的预期:
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
的使用(不带参数)与 (type, object) 与 (type, type)。我使用了 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
的子 class,但我想重复验证(更多重复代码)。通过 sub-classing 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__
,将参数更改为 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
的部分目的:避免引用基础 class 以使其更易于维护。有没有一种方法可以使这项工作不需要在 __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
class 本身。所以,当方法的实现引用了cls
,也就是引用了Offset16
。所以,
return super(cls, cls).__new__(cls, val)
在这种情况下等同于
return super(Offset16, Offset16).__new__(Offset16, val)
现在我们可能认为 super
返回基数 class,但它在提供参数时的语义更加微妙:super
正在解析对方法的引用和争论会影响该决议的发生方式。如果没有提供参数,super().__new__
是直接 superclass 中的方法。提供参数时,会影响搜索。特别是对于 super(type1, type2)
,将搜索 type2
的 MRO(方法解析顺序)以查找 type1
的出现,以及 type1[ 之后的 class =119=] 将使用该序列。
(这在 documentation of super
中有解释,尽管措辞可能更清楚。)
Offset16
的 MRO 是 (Offset16, uint16, int, object)。因此
return super(Offset16, Offset16).__new__(Offset16, val)
解析为
return uint16.__new__(Offset16, val)
当以这种方式调用uint16.__new__
时,传递给它的class参数是Ofset16
,而不是uint16
。结果,当它的实现有
return super(cls, cls).__new__(cls, val)
将再次解析为
return uint16.__new__(Offset16, val)
这就是我们以无限循环结束的原因。
但在 Offset16
的更改定义中,
class Offset16(uint16):
def __new__(cls, val):
return super(uint16, cls).__new__(cls, val)
最后一行相当于
return super(uint16, Offset16).__new__(Offset16, val)
并且根据 Offset16
的 MRO 和上面提到的 super
的语义,解析为
return int.__new__(Offset16, val)
这解释了为什么更改后的定义会导致不同的行为。
第二:最好的方法是什么?
评论中提供了可能适合不同情况的不同备选方案。
@juanpa.arrivillaga 建议(假设 Python3)简单地使用 super()
而不带参数。对于问题中采用的方法,这是有道理的。将参数传递给 super
的原因是为了操纵 MRO 搜索。在这个简单的 class 层次结构中,不需要这样做。
@Jason Yang 建议直接引用特定的 superclass 而不是使用 super
。例如:
class Offset16(uint16):
def __new__(cls, val):
return uint16.__new__(cls, val)
对于这种简单的情况,这完全没问题。但对于具有更复杂 class 关系的其他场景,它可能不是最好的。请注意,例如,uint16
在上面重复了。如果 subclass 有几个包装(而不是替换)superclass 方法的方法,就会有很多重复引用,并且对 class 层次结构进行更改会导致硬-分析错误。避免此类问题是使用 super
的预期好处之一。
最后,@Adam.Er8建议简单地使用
Offset16 = uint16
这确实很简单。需要注意的是 Offset16
实际上只是 uint16
的一个别名;它不是单独的 class。例如:
>>> Offset16 = uint16
>>> x = Offset16(24)
>>> type(x)
<class 'uint16'>
所以,这可能没问题只要应用程序永远不需要有实际的类型区分。