Как добиться полиморфизма в Python C API?
Я пишу functools.partial
альтернатива объекта, которая накапливает аргументы, пока их число не станет достаточным для совершения вызова.
Я использую C API, и у меня есть tp_call
реализация, которая при вызове возвращает измененную версию self или PyObject*
,
Сначала я следовал руководству " Определение новых типов", а потом понял, что просто не могу возвращать разные типы (PyObject *
а также MyObject*
) от tp_call
реализация. Тогда я пытался не использовать struct
с MyObject*
определение и использование PyObject_SetAttrString
в tp_init
вместо этого, как мы делаем это в Python. Но в этом случае я получил AttributeError
потому что вы не можете установить произвольные атрибуты на object
экземпляры в Python.
Что мне нужно здесь, чтобы сделать мой tp_call
реализация полиморфная, и сделать ее способной вернуть либо MyObject
который является подклассом PyObject
, или же PyObject
набери сам.
Какой разумный способ сделать это?
ОБНОВЛЕНИЕ № 0
Вот код:
static PyObject *Curry_call(Curry *self, PyObject *args,
PyObject *kwargs) {
PyObject * old_args = self->args;
self->args = PySequence_Concat(self->args, args);
Py_DECREF(old_args);
if (self->kwargs == NULL && kwargs != NULL) {
self->kwargs = kwargs;
Py_INCREF(self->kwargs);
} else if (self->kwargs != NULL && kwargs != NULL) {
PyDict_Merge(self->kwargs, kwargs, 1);
}
if ((PyObject_Size(self->args) +
(self->kwargs != NULL ? PyObject_Size(self->kwargs) : 0)) >=
self->num_args) {
return PyObject_Call(self->fn, self->args, self->kwargs);
} else {
return (PyObject *)self;
}
}
ОБНОВЛЕНИЕ № 1
Почему я изначально отказался от этой реализации - потому что я получаю segfault с ним при последующих вызовах частичного объекта. Я думал, что это происходит из-за кастинга Curry *
в PyObject*
проблемы. Но теперь я исправил ошибку, добавив Py_INCREF(self);
до возвращения (PyObject *)self;
, Очень странно для меня. Должен ли я действительно INCREF self, если я возвращаю его по правилам владения C API?
2 ответа
Если вы определили MyObject
введите правильно, вы должны иметь возможность просто разыграть MyObject *
к PyObject *
и верни это. Первый член MyObject
это PyObject
и C позволяет вам привести указатель на структуру к указателю на первый член структуры и наоборот. Я полагаю, что эта функция существует специально для таких вещей.
Я не знаю весь ваш код, но пока MyObject
это PyObject
(совместимый, то есть имеет те же поля "заголовка", убедитесь, что у вас есть поле длины), CPython предназначен для того, чтобы просто принимать MyObject в качестве PyObject; просто приведите указатель к PyObject, прежде чем возвращать его.
Как вы можете видеть здесь, это одна из вещей, которая удобна при использовании C++: у вас могут быть подклассы с безопасностью типов, и вам не нужно беспокоиться о том, что кто-то просто скопирует более половины экземпляра вашего подкласса, например,
РЕДАКТИРОВАТЬ: потому что его спросили "не это небезопасно": да. Это. Но это так же небезопасно, как обработка типов в пользовательском коде; CPython позволяет вам сделать это, потому что он хранит и проверяет PyTypeObject *ob_type
член PyObject
структура содержится. Это примерно так же безопасно, как, например, проверка типа C++ во время выполнения - но она реализована разработчиками python, а не разработчиками GCC/clang/MSVC/icc/....