Подтвердить что ты не робот

Как динамически создавать производный тип в C-API Python

Предположим, что мы имеем тип Noddy, как определено в учебнике о написании модулей расширения C для Python. Теперь мы хотим создать производный тип, переписывая только __new__() метод Noddy.

В настоящее время я использую следующий подход (проверка ошибок удалена для удобочитаемости):

PyTypeObject *BrownNoddyType =
    (PyTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0);
BrownNoddyType->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
BrownNoddyType->tp_name = "noddy.BrownNoddy";
BrownNoddyType->tp_doc = "BrownNoddy objects";
BrownNoddyType->tp_base = &NoddyType;
BrownNoddyType->tp_new = BrownNoddy_new;
PyType_Ready(BrownNoddyType);

Это работает, но я не уверен, что это правильный способ сделать это. Я бы предположил, что мне нужно установить флаг Py_TPFLAGS_HEAPTYPE, потому что я динамически выделяю объект типа в куче, но делаю это приводит к segfault в интерпретаторе.

Я также думал о явном вызове type() с помощью PyObject_Call() или аналогичного, но я отбросил эту идею. Мне нужно было бы обернуть функцию BrownNoddy_new() в объект функции Python и создать сопоставление словаря __new__ с этим функциональным объектом, что кажется глупым.

Каков наилучший способ сделать это? Правильно ли мой подход? Есть ли функция интерфейса, которую я пропустил?

Update

Есть два потока в соответствующем разделе в списке рассылки python-dev (1) (2). Из этих потоков и нескольких экспериментов я выводю, что я не должен устанавливать Py_TPFLAGS_HEAPTYPE, если тип не назначен вызовом type(). В этих потоках существуют разные рекомендации: лучше ли назначать тип вручную или вызывать type(). Я был бы доволен последним, если бы я знал, какой рекомендуемый способ обернуть функцию C, которая должна находиться в слоте tp_new. Для обычных методов этот шаг был бы простым - я мог бы просто использовать PyDescr_NewMethod(), чтобы получить подходящий объект-обертку. Я не знаю, как создать такой объект-оболочку для моего метода __new__(), хотя, может быть, мне нужна недокументированная функция PyCFunction_New() для создания такого объекта-оболочки.

4b9b3361

Ответ 1

Я столкнулся с той же проблемой, когда я изменял расширение, совместимое с Python 3, и нашел эту страницу, когда я пытался ее решить.

В конце концов я решил его прочитать, прочитав исходный код интерпретатора Python, PEP 0384 и документацию для C-API.

Установка флага Py_TPFLAGS_HEAPTYPE указывает интерпретатору переделать ваш PyTypeObject как PyHeapTypeObject, который содержит дополнительные элементы, которые также должны быть выделены. В какой-то момент интерпретатор пытается ссылаться на этих дополнительных членов, и, если вы оставите их нераспределенными, это вызовет segfault.

Python 3.2 представил структуры C PyType_Slot и PyType_Spec и функцию C PyType_FromSpec, которые упрощают создание динамических типов. В двух словах вы используете PyType_Slot и PyType_Spec, чтобы указать tp_* членов PyTypeObject, а затем вызовите PyType_FromSpec, чтобы выполнить грязную работу по выделению и инициализации памяти.

Из PEP 0384 мы имеем:

typedef struct{
  int slot;    /* slot id, see below */
  void *pfunc; /* function pointer */
} PyType_Slot;

typedef struct{
  const char* name;
  int basicsize;
  int itemsize;
  int flags;
  PyType_Slot *slots; /* terminated by slot==0. */
} PyType_Spec;

PyObject* PyType_FromSpec(PyType_Spec*);

(Вышеупомянутая не является литеральной копией из PEP 0384, которая также включает const char *doc в качестве члена PyType_Spec. Но этот член не отображается в исходном коде.)

Чтобы использовать их в исходном примере, предположим, что у нас есть структура C, BrownNoddy, которая расширяет структуру C для базового класса Noddy. Тогда мы имели бы:

PyType_Slot slots[] = {
    { Py_tp_doc, "BrownNoddy objects" },
    { Py_tp_base, &NoddyType },
    { Py_tp_new, BrownNoddy_new },
    { 0 },
};
PyType_Spec spec = { "noddy.BrownNoddy", sizeof(BrownNoddy), 0,
                      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots };
PyTypeObject *BrownNoddyType = (PyTypeObject *)PyType_FromSpec(&spec);

Это должно делать все в исходном коде, включая вызов PyType_Ready, плюс то, что необходимо для создания динамического типа, включая установку Py_TPFLAGS_HEAPTYPE, а также выделение и инициализацию дополнительной памяти для PyHeapTypeObject.

Надеюсь, что это полезно.

Ответ 2

Я извиняюсь перед этим, если этот ответ ужасен, но вы можете найти реализацию этой идеи в PythonQt, в частности, я думаю следующие файлы могут быть полезными ссылками:

Этот фрагмент из PythonQtClassWrapper_init выскакивает на меня как несколько интересный:

static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args, PyObject* kwds)
{
  // call the default type init
  if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) {
    return -1;
  }

  // if we have no CPP class information, try our base class
  if (!self->classInfo()) {
    PyTypeObject*  superType = ((PyTypeObject *)self)->tp_base;

    if (!superType || (superType->ob_type != &PythonQtClassWrapper_Type)) {
      PyErr_Format(PyExc_TypeError, "type %s is not derived from PythonQtClassWrapper", ((PyTypeObject*)self)->tp_name);
      return -1;
    }

    // take the class info from the superType
    self->_classInfo = ((PythonQtClassWrapper*)superType)->classInfo();
  }

  return 0;
}

Стоит отметить, что PythonQt использует генератор оболочек, поэтому он не совсем соответствует тому, что вы просите, но лично я думаю, что попытка перехитрить vtable - не самый оптимальный дизайн. В принципе, существует множество различных генераторов оболочки С++ для Python, и люди используют их по уважительной причине - они задокументированы, есть примеры, плавающие в результатах поиска и переполнении стека. Если вы вручную откажитесь от решения, которое никто не видел раньше, им будет намного сложнее отлаживать, если они столкнутся с проблемами. Даже если он закрыт, следующий парень, который должен его поддерживать, будет царапать его голову, и вам придется объяснять это каждому новому человеку, который приходит.

Как только вы получите генератор кода, все, что вам нужно сделать, это поддерживать базовый код С++, вам не нужно вручную обновлять или изменять свой код расширения. (Что, вероятно, не слишком далеко от заманчивого решения, с которым вы пошли)

Предлагаемое решение является примером нарушения безопасности типа, которое недавно введенная PyCapsule обеспечивает немного больше защиты против (при использовании по назначению).

Итак, хотя это возможно, возможно, это не лучший выбор для долгосрочного выбора для реализации производных/подклассов, а скорее оберните код и позвольте vtable делать то, что он делает лучше всего, и когда у нового парня есть вопросы, вы можете просто указать он в документации для независимо решение подходит лучший.

Это только мое мнение.: D

Ответ 3

Один из способов попытаться понять, как это сделать, - создать его версию с помощью SWIG. Посмотрите, что он производит, и посмотрите, совпадает ли это или по другому. Из того, что я могу сказать, люди, которые пишут SWIG, имеют глубокое понимание расширения Python. Не может повредить, чтобы увидеть, как они делают что-то в любом случае. Это может помочь вам понять эту проблему.