Я пишу расширения 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: Чтобы понять, почему я пометил ответ Свена Марнача как ответ, см. Комментарии ниже этого ответа.