Reputation: 4072
I'm using a third-party module which provides classes that inherit from tuple
. However, I'd like to add some functionality to these classes, so I have sub-classed. The resultant inheritance hierarchy looks like:
MyClass
-> LibClass
-> tuple
Is there any reason inheritance from a tuple
sub-class should be expected to fail?
Everything seems to be peachy at first. However, utilising a slice (instance[:6]
) to access a range of values from an instance of MyClass
results in an error like:
SystemError: <method-wrapper '__getitem__' of LibClass object at 0x1010101010> returned NULL without setting an error
Doing the exact same thing with an instance of LibClass
works flawlessly.
To further add to the mystery, regular indexed access (instance[5]
) on an instance of MyClass
works flawlessly.
Obviously tuple
inheritance isn't exactly like regular class inheritance (i.e. __new__
must be overridden rather than __init__
). However, to my knowledge LibClass
is doing so correctly e.g.
def __new__(cls, *members):
mat = [x * 1.0 for x in members] + [0.0, 0.0, 1.0]
return tuple.__new__(cls, mat)
I don't believe implementing __new__
in MyClass
is necessary given the implementation in LibClass
correctly passes through cls
(admittedly I had to fork the library to achieve that). Nonetheless, for posterity I did also try implementing __new__
directly in MyClass
(just copied and pasted the LibClass
implementation).
I should also note that I'm not doing anything wacky in MyClass
. In fact if I do nothing at all the problem still persists e.g.
class MyClass(lib.LibClass):
pass
Another thing worth noting, LibClass
does not have a custom __getitem__
implementation - it's simply inheriting that behaviour from tuple
.
Python 3.6.1
LibClass
is in fact Affine
from planar
, my fork can be found here:
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
It was pointed out in the comments that in the above reproduction identity()
returns a constant. So it really shouldn't be failing. I can't explain that. However, I should probably add that it was a pretty poor reproduction by me. My real world usage is closer to:
>>> 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)
which also fails in the same fashion.
Mind you, the failure with the constant really has me scratching my head.
planar
specificTrying different Python versions, it fails similarly:
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
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
However, I was not able to reproduce the problem when reduced to its simplest form i.e. (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)
I also tried moving the definition of LibClass
to a separate lib.py
to ensure the error wasn't somehow related to Python modules, but it worked as above.
So the issue is something specific to planar
and/or its Affine
class. It'd be nice to know exactly what is causing the problem though.
Upvotes: 2
Views: 435
Reputation: 280564
It turns out there is indeed a buggy extension module involved. planar
isn't using your edited planar.transform
module at all; it's using planar.c
, a C implementation of planar
's functionality with its own Affine
class.
At least part of the problem seems to be due to a bug in 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);
}
where it returns NULL
without setting an IndexError
for an out-of-range index.
planar
is no longer maintained, so a bug report won't be handled. There may be better modules to use.
Upvotes: 1