I'm writing Python 3 extensions in C++ and I'm trying to find a way to check if a PyObject
is related to a type (struct) defining its instance layout. I'm only interested in static-size PyObject
, not PyVarObject
. The instance layout is defined by a struct with certain well-defined layout: mandatory PyObject
header and (optional) user-defined members.
Below, is example of PyObject
extension based on the well-known Noddy example in Defining New Types:
// Noddy struct specifies PyObject instance layout
struct Noddy {
PyObject_HEAD
int number;
};
// type object corresponding to Noddy instance layout
PyTypeObject NoddyType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"noddy.Noddy", /*tp_name*/
sizeof(Noddy), /*tp_basicsize*/
0, /*tp_itemsize*/
...
Noddy_new, /* tp_new */
};
It is important to notice that the Noddy
is a type, a compile-time entity,
but NoddyType
is an object present in memory at run-time.
The only obvious relation between the Noddy
and NoddyType
seems to be
value of sizeof(Noddy)
stored in tp_basicsize
member.
The hand-written inheritance implemented in Python specifies rules which allow to cast between PyObject
and type used to declare the instance layout of that particular PyObject
:
PyObject* Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
// When a Python object is a Noddy instance,
// its PyObject* pointer can be safely cast to Noddy
Noddy *self = reinterpret_cast<Noddy*>(type->tp_alloc(type, 0));
self->number = 0; // initialise Noddy members
return reinterpret_cast<PyObject*>(self);
}
In circumstances like various slot functions, it is safe to assume "a Python object is a Noddy" and cast without any checks. However, sometimes it is necessary to cast in other situations, then it feels like a blind conversion:
void foo(PyObject* obj)
{
// How to perform safety checks?
Noddy* noddy = reinterpret_cast<Noddy*>(obj);
...
}
It is possible to check sizeof(Noddy) == Py_TYPE(obj)->tp_basicsize
, but it is insufficient solution due to:
1) If a user will derive from Noddy
class BabyNoddy(Noddy):
pass
and obj
in foo
points to instance of the BabyNoddy
, Py_TYPE(obj)->tp_basicsize
is diferent.
But, it is still safe to cast to reinterpret_cast<Noddy*>(obj)
to get pointer to the instance layout part.
2) There can be other struct declaring instance layout of the same size as Noddy
:
struct NeverSeenNoddy {
PyObject_HEAD
short word1;
short word2;
};
In fact, C langauge level, NeverSeenNoddy
struct is compatible with the NoddyType
type object - it can fit into NoddyType
. So, cast could be perfectly fine.
So, my big question is this:
Is there any Python policy which could be used to determine if a PyObject
is compatible with the Noddy
instance layout?
Any way to check if PyObject*
points to the object part which is embedded in the Noddy
?
If not policy, is there any hack possible?
EDIT: There are a few questions which seem to be similar, but in my opinion they are different to the one I have asked. For example: Accessing the underlying struct of a PyObject
EDIT2: In order to understand why I marked Sven Marnach's response as the answer, see comments below that answer.