Как определить структуру, используемую для объявления макета экземпляра PyObject?

1

Я пишу расширения Python 3 в C++, и я пытаюсь найти способ проверить, PyObject ли PyObject с типом (struct), определяющим его макет экземпляра. Меня интересует только статический размер 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 в точках foo для экземпляра BabyNoddy, Py_TYPE(obj)->tp_basicsize отличается. Но, по-прежнему безопасно использовать для reinterpret_cast<Noddy*>(obj) чтобы получить указатель на часть макета экземпляра.

2) Может существовать другая структура, объявляющая макет экземпляра того же размера, что и Noddy:

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

Фактически уровень C langauge, структура NeverSeenNoddy совместима с NoddyType типа NoddyType - он может вписываться в NoddyType. Итак, отливка может быть прекрасной.

Итак, мой большой вопрос:

Есть ли какая-либо политика Python, которая может быть использована для определения совместимости PyObject с макетом экземпляра Noddy?

Любой способ проверить, PyObject* ли PyObject* на объектную часть, встроенную в Noddy?

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

EDIT: Есть несколько вопросов, которые кажутся похожими, но, на мой взгляд, они отличаются от того, что я спросил. Например: доступ к базовой структуре PyObject

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

Теги:
python-3.x
pyobject

2 ответа

2
Лучший ответ

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

PyObject_IsInstance(obj, &NoddyType)

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

  • 0
    Спасибо за ответ, но я знаю PyObject_IsInstance (и все другие API, работающие с типами), и они не обеспечивают решение моего вопроса. Как вы указали, они проверяют NoddyType (объект времени выполнения), но я спрашиваю о проверке, не связан ли obj с Noddy типа C ++ (см. Struct в моем примере) - struct Noddy является расширением PyObject, определяет макет экземпляра PyObject ,
  • 0
    @mloskot: Может быть, мой ответ не очень ясен. Вы задаете по существу вопросы, отмеченные 1) и 2) в вашем посте. PyObject_IsInstance() является ответом на 1). Что касается 2), мне кажется, это проблема дизайна. Если вы хотите сделать что-то подобное, убедитесь, что все участвующие типы имеют общий базовый тип. Это мой ответ на вопрос 2). Так как это не отвечает на ваш вопрос? (Обратите внимание, что я знаю, что 1) и 2) на самом деле не перечисляют вопросы, а скорее проблемные случаи в вашем посте.)
Показать ещё 6 комментариев
1

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

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

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

Предполагая, что NeverSeenNoddy является макетом типа NeverSeenNoddy_Type, вы никогда не должны бросать в NeverSeenNoddy если PyObject_IsInstance(obj, &NeverSeenNoddy_Type) является ложным.

Если вы хотите иметь два типа C-уровня с общими полями, вы должны получить оба типа из общей базы, которая имеет только общие поля в макете экземпляра.

Затем подтипы должны включать базовый макет в верхней части их макетов:

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

Затем, если PyObject_IsInstance(obj, &SubNoddy_Type) вернет true, вы можете SubNoddy к SubNoddy и получить доступ к полю extra_field. Если PyObject_IsInstance(obj, &Noddy_Type) возвращает true, вы можете PyObject_IsInstance(obj, &Noddy_Type) к Noddy и получить доступ к общим полям.

  • 0
    +1 Как и в ответе Свена Марнаха, это помогает мне понять ошибки в моих предположениях об объектах PyObject и типах.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню