Как определить структуру, используемую для объявления макета экземпляра PyObject? - PullRequest
2 голосов
/ 11 декабря 2011

Я пишу расширения Python 3 на C ++ и пытаюсь найти способ проверить, относится ли PyObject к типу (структуре), определяющему его макет экземпляра.Меня интересует только статический размер 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 является нодди» и приведен без каких-либочеки.Однако иногда необходимо разыграть в других ситуациях, тогда это похоже на слепое преобразование:

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 с * 1055?* макет экземпляра?

Есть ли способ проверить, указывает ли PyObject* на ту часть объекта, которая встроена в Noddy?

Если нет политики, возможен ли взлом?

РЕДАКТИРОВАТЬ: Есть несколько вопросов, которые кажутся похожими, но, на мой взгляд, они отличаются от того, который я задавал.Например: Доступ к базовой структуре PyObject

EDIT2: Чтобы понять, почему я пометил ответ Свена Марнача как ответ, см. Комментарии ниже этого ответа.

Ответы [ 2 ]

3 голосов
/ 11 декабря 2011

В Python вы можете проверить, имеет ли obj тип Noddy или производный тип, с помощью теста isinstance(obj, Noddy). Проверка в C-API, имеет ли PyObject *obj тип NoddyType или производный тип, в основном такая же, вы используете PyObject_IsInstance():

PyObject_IsInstance(obj, &NoddyType)

Что касается вашего второго вопроса, нет способа достичь этого, и если вы считаете, что вам это нужно, у вашего дизайна есть серьезные недостатки. Во-первых, было бы лучше извлечь NeverSeenNoddyType из NoddyType - тогда вышеупомянутая проверка также распознает объект производного типа как экземпляр NoddyType.

1 голос
/ 11 декабря 2011

Поскольку каждый объект начинается с PyObject_HEAD, всегда безопасно получить доступ к полям, определенным этим заголовком.Одним из полей является ob_type (доступ к нему обычно осуществляется с помощью макроса Py_TYPE).Если это указывает на NoddyType или любой другой тип, производный от NoddyType (что говорит вам PyObject_IsInstance), то вы можете предположить, что макет объекта соответствует struct Noddy.

Другими словамиобъект совместим с макетом экземпляра Noddy, если его Py_TYPE указывает на NoddyType или на любой из его подклассов.

Во втором вопросе приведение не будет правильным.Макеты Noddy и NeverSeenNoddy различны, даже если размер может быть одинаковым.

Если предположить, что NeverSeenNoddy является макетом типа NeverSeenNoddy_Type, вы никогда не должны приводить к NeverSeenNoddyесли PyObject_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 и получить доступ к общим полям.

...