【发布时间】:2017-05-12 16:54:49
【问题描述】:
我正在使用一个第三方模块,它提供从tuple 继承的类。但是,我想为这些类添加一些功能,所以我进行了子类化。生成的继承层次结构如下所示:
MyClass -> LibClass -> tuple
从tuple 子类继承是否有任何理由预期会失败?
血淋淋的细节
起初一切似乎都很美好。但是,使用切片 (instance[:6]) 访问来自 MyClass 实例的一系列值会导致如下错误:
SystemError: <method-wrapper '__getitem__' of LibClass object at 0x1010101010> returned NULL without setting an error
对LibClass 的实例执行完全相同的操作完美无缺。
为了进一步增加神秘感,MyClass 实例上的常规索引访问 (instance[5]) 完美无缺。
显然tuple 继承与常规类继承完全不同(即__new__ 必须被覆盖而不是__init__)。但是,据我所知LibClass 这样做是正确的,例如
def __new__(cls, *members):
mat = [x * 1.0 for x in members] + [0.0, 0.0, 1.0]
return tuple.__new__(cls, mat)
我不认为在MyClass 中实现__new__ 是必要的,因为LibClass 中的实现正确地通过cls(诚然,我不得不分叉库来实现这一点)。尽管如此,为了后代,我也尝试直接在MyClass 中实现__new__(只是复制并粘贴了LibClass 实现)。
我还应该注意,我在MyClass 中没有做任何古怪的事情。事实上,如果我什么都不做问题仍然存在,例如
class MyClass(lib.LibClass):
pass
还有一点值得注意,LibClass 没有自定义的__getitem__ 实现——它只是从tuple 继承该行为。
Python 3.6.1
额外的血腥(真实)细节
LibClass 实际上是来自planar 的Affine,我的fork 可以在这里找到:
https://github.com/Benjamin-Dobell/planar/blob/master/lib/planar/transform.py
复制
pip install git+https://github.com/Benjamin-Dobell/planar#egg=planar
python
>>> import planar
>>> class Affine(planar.Affine):
... pass
...
>>> planar.Affine.identity()[:6]
(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
>>> Affine.identity()[:6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SystemError: <method-wrapper '__getitem__' of Affine object at 0x10e2b9ba8> returned NULL without setting an error
在 cmets 中指出,在上面的复制中 identity() 返回一个常量。所以它真的不应该失败。我无法解释。但是,我可能应该补充一点,这是我的一个非常糟糕的复制品。我的实际使用情况更接近:
>>> Affine.translation((0, 0))[:6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SystemError: <method-wrapper '__getitem__' of Affine object at 0x10f8d5ee8> returned NULL without setting an error
>>> planar.Affine.translation((0, 0))[:6]
(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
同样会失败。
请注意,常数的失败真的让我摸不着头脑。
planar 特定的东西
尝试不同的 Python 版本,同样失败:
3.3.6
Python 3.3.6 (default, Apr 12 2017, 17:20:32)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import planar
>>> class Affine(planar.Affine):
... pass
...
>>> planar.Affine.identity()[:6]
(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
>>> Affine.identity()[:6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SystemError: NULL result without error in PyObject_Call
2.7.11
Python 2.7.11 (default, May 2 2016, 14:38:51)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import planar
>>> class Affine(planar.Affine):
... pass
...
>>> planar.Affine.identity()[:6]
(1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
>>> Affine.identity()[:6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
SystemError: NULL result without error in PyObject_Call
但是,当简化为最简单的形式(即(Python 2.7.11))时,我无法重现该问题:
>>> class LibClass(tuple):
... def __new__(cls, *members):
... return tuple.__new__(cls, *members)
...
>>> class MyClass(LibClass):
... pass
...
>>> LibClass((1, 2, 3, 4, 5))[:3]
(1, 2, 3)
>>> MyClass((1, 2, 3, 4, 5))[:3]
(1, 2, 3)
我还尝试将 LibClass 的定义移动到单独的 lib.py 以确保错误与 Python 模块无关,但它的工作原理与上述相同。
所以问题是特定于 planar 和/或其 Affine 类的。很高兴知道究竟是什么导致了问题。
【问题讨论】:
-
这应该不会失败。他们的子类是用 C 写的吗?您能否告诉我们真正的
LibClass是什么,和/或提供源代码链接以便我们查看? -
“我不得不分叉库来实现这一点” - 你能链接到你的分叉吗?
-
@user2357112 我已经用底部的真实代码链接更新了问题。谢谢。
-
嘿,
identity()无论您是在planar.Affine还是子类上调用它都会返回相同的对象。这是第二次切片失败的 same 对象。 -
啊,找到了。确实涉及到一个 C 扩展模块 -
planar根本没有使用您编辑的planar.transform模块。它使用planar.c,一个C 版本。我现在正在寻找错误。
标签: python python-3.x inheritance tuples