如何确定用于声明PyObject实例布局的struct?

2024-04-25 14:46:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我在C++中编写Python 3扩展,我想找到一种方法来检查^ {< CD1>}是否与定义其实例布局的类型(Stult)有关。我只对静态大小PyObject感兴趣,而不是{}。实例布局由具有某些定义良好的布局的结构定义:强制的PyObject头和(可选)用户定义成员。在

下面是基于众所周知的Noddy example in Defining New Types扩展的PyObject示例:

// 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和{}之间唯一明显的关系似乎是 存储在tp_basicsize成员中的sizeof(Noddy)的值。在

Python中实现的手写继承指定了允许在PyObject和用于声明该特定PyObject的实例布局的类型之间转换的规则:

^{pr2}$

在类似于各种slot函数的情况下,可以安全地假设“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中的obj指向BabyNoddy的实例,Py_TYPE(obj)->tp_basicsize是不同的。 但是,仍然可以安全地强制转换到reinterpret_cast<Noddy*>(obj)以获取指向实例布局部分的指针。在

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

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

事实上,C语言级别的NeverSeenNoddy结构与NoddyType类型的对象兼容,它可以放入NoddyType。所以,演员阵容可以很好。在

所以,我的大问题是:

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

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

如果不是策略,是否有黑客攻击的可能?在

编辑:有几个问题看起来很相似,但在我看来,它们与我所问的不同。例如:Accessing the underlying struct of a PyObject

编辑2:为了理解我为什么把斯文·马纳赫的回答作为答案,请看下面的评论。在


Tags: 对象实例obj类型定义布局结构head
2条回答

在Python中,可以通过使用测试isinstance(obj, Noddy)来检查objNoddy类型还是派生类型。在C-API中测试某个PyObject *objNoddyType类型还是派生类型基本相同,可以使用^{}

PyObject_IsInstance(obj, &NoddyType)

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

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

换句话说,如果对象的Py_TYPE指向NoddyType或其任何子类,则该对象与Noddy实例布局兼容。在

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

假设NeverSeenNoddyNeverSeenNoddy_Type类型的布局,如果PyObject_IsInstance(obj, &NeverSeenNoddy_Type)为false,则不应强制转换为NeverSeenNoddy。在

如果希望有两个具有公共字段的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并访问公共字段。在

相关问题 更多 >