4

我正在用 C++ 编写 Python 3 扩展,并试图找到一种方法来检查 aPyObject是否与定义其实例布局的类型(结构)相关。我只对 static-size 感兴趣PyObject,而不是PyVarObject. 实例布局由具有某些明确定义的布局的结构定义:强制PyObject标头和(可选)用户定义的成员。

下面是PyObject基于定义新类型中著名的Noddy 示例的扩展示例:

// 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 */
};

重要的是要注意它Noddy是一个类型,一个编译时实体,但NoddyType它是一个在运行时存在于内存中的对象。Noddy和之间唯一明显的关系NoddyType似乎是sizeof(Noddy)存储在tp_basicsize成员中的值。

在 Python 中实现的手写继承指定了允许在PyObject用于声明该特定实例布局的类型之间进行转换的规则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);
}

在各种槽函数等情况下,可以安全地假设“Python 对象是 Noddy”并在没有任何检查的情况下进行强制转换。但是,有时需要在其他情况下强制转换,然后感觉就像是在盲目转换:

void foo(PyObject* obj)
{
    // How to perform safety checks?
    Noddy* noddy = reinterpret_cast<Noddy*>(obj);
    ...
}

可以检查sizeof(Noddy) == Py_TYPE(obj)->tp_basicsize,但由于以下原因,它是不充分的解决方案:

1) 如果用户将来自Noddy

class BabyNoddy(Noddy):
    pass

并且指向 , 的实例是obj不同的。但是,强制转换以获取指向实例布局部分的指针仍然是安全的。fooBabyNoddyPy_TYPE(obj)->tp_basicsizereinterpret_cast<Noddy*>(obj)

2)可以有其他结构声明实例布局的大小相同Noddy

struct NeverSeenNoddy {
    PyObject_HEAD
    short word1;
    short word2;
};

事实上,在 C 语言级别,NeverSeenNoddystruct 与 object 类型兼容NoddyType——它可以适应NoddyType. 所以,演员阵容可能非常好。

所以,我的大问题是:

是否有任何 Python 策略可用于确定 aPyObject是否与Noddy实例布局兼容?

有什么方法可以检查是否PyObject*指向嵌入的对象部分Noddy

如果没有政策,是否有任何黑客可能?

编辑:有几个问题似乎相似,但在我看来,它们与我提出的问题不同。例如:访问 PyObject 的底层结构

EDIT2:为了理解为什么我将 Sven Marnach 的回复标记为答案,请参阅该答案下方的评论。

4

2 回答 2

5

在 Python 中,您可以使用 test来检查obj是类型还是派生类型。C-API 中的一些是类型还是派生类型的测试基本相同,您使用:Noddyisinstance(obj, Noddy)PyObject *objNoddyTypePyObject_IsInstance()

PyObject_IsInstance(obj, &NoddyType)

至于你的第二个问题,没有办法做到这一点,如果你认为你需要这个,你的设计有严重的缺陷。最好从一开始就派生NeverSeenNoddyTypeNoddyType——然后上面的检查也会将派生类型的对象识别为NoddyType.

于 2011-12-11T00:46:38.413 回答
1

因为每个对象都以 开头PyObject_HEAD,所以访问此标头定义的字段始终是安全的。其中一个字段是ob_type(通常使用Py_TYPE宏访问)。如果 this 指向NoddyType或派生自任何其他类型NoddyType(这就是PyObject_IsInstance告诉你的),那么你可以假设对象的布局是struct Noddy.

换句话说,如果一个对象指向或它的任何子类,它就与Noddy实例布局兼容。Py_TYPENoddyType

在第二个问题中,演员阵容不会很好。Noddy和的布局NeverSeenNoddy不同,即使大小可能相同。

假设这NeverSeenNoddy是一个NeverSeenNoddy_Type类型的布局,你不应该强制转换为NeverSeenNoddyifPyObject_IsInstance(obj, &NeverSeenNoddy_Type)为 false。

如果您希望有两个具有公共字段的 C 级类型,则应从在实例布局中仅具有公共字段的公共基础派生这两种类型。

然后,子类型应在其布局顶部包含基本布局:

struct SubNoddy {
    // No PyObject_HEAD because it's already in Noddy
    Noddy noddy;
    int extra_field;
};

然后,如果PyObject_IsInstance(obj, &SubNoddy_Type)返回 true,您可以强制转换SubNoddy并访问该extra_field字段。如果PyObject_IsInstance(obj, &Noddy_Type)返回 true,您可以强制转换Noddy并访问公共字段。

于 2011-12-11T02:30:02.783 回答