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

时间:2011-12-11 00:32:38

标签: python api python-3.x pyobject

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

以下是基于众所周知的Noddy example in Defining New TypesPyObject扩展示例:

// 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是运行时存在于内存中的对象。 NoddyNoddyType之间唯一明显的关系似乎是 存储在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中的foo指向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

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

2 个答案:

答案 0 :(得分:3)

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

PyObject_IsInstance(obj, &NoddyType)

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

答案 1 :(得分:1)

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

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

在第二个问题中,演员阵容不会很好。 NoddyNeverSeenNoddy的布局不同,即使尺寸可能相同。

假设NeverSeenNoddyNeverSeenNoddy_Type类型的布局,如果NeverSeenNoddy为假,则不应转发PyObject_IsInstance(obj, &NeverSeenNoddy_Type)

如果您想要两个具有公共字段的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并访问公共字段。