【问题标题】:Inheriting from a tuple subclass从元组子类继承
【发布时间】: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 实际上是来自planarAffine,我的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


【解决方案1】:

事实证明确实涉及到一个错误的扩展模块。 planar 根本没有使用您编辑的 planar.transform 模块;它使用planar.c,这是planar 的功能的C 实现,具有自己的Affine 类。

至少部分问题似乎是由于Affine_getitem 中的错误:

static PyObject *
Affine_getitem(PlanarAffineObject *self, Py_ssize_t i)
{
    double m;

    assert(PlanarAffine_Check(self));
    if (i < 6) {
        m = self->m[i];
    } else if (i < 8) {
        m = 0.0;
    } else if (i == 8) {
        m = 1.0;
    } else {
        return NULL;
    }
    return PyFloat_FromDouble(m);
}

它返回NULL 而不为超出范围的索引设置IndexError

planar 不再维护,因此不会处理错误报告。可能有更好的模块可供使用。

【讨论】:

    猜你喜欢
    • 2017-07-12
    • 1970-01-01
    • 1970-01-01
    • 2010-12-21
    • 1970-01-01
    • 2020-03-19
    • 2011-01-10
    • 1970-01-01
    • 2016-09-02
    相关资源
    最近更新 更多