Выделение объектов Python C-API
-
05-09-2019 - |
Вопрос
Я хочу использовать операторы new и delete для создания и уничтожения моих объектов.
Проблема в том, что python, похоже, разбивает ее на несколько этапов.tp_new, tp_init и tp_alloc для создания и tp_del, tp_free и tp_dealloc для уничтожения.Однако в c ++ есть только new, который выделяет и полностью создает объект, и delete, который уничтожает и освобождает объект.
Какой из методов python tp_ * мне нужно предоставить и что они должны делать?
Также я хочу иметь возможность создавать объект непосредственно на c ++, например, "PyObject * obj = new MyExtensionObject(args);" Нужно ли мне также каким-либо образом перегружать оператор new для поддержки этого?
Я также хотел бы иметь возможность подклассировать свои типы расширений в python, есть ли что-нибудь особенное, что мне нужно сделать, чтобы поддержать это?
Я использую python 3.0.1.
Редактировать:хорошо, tp_init, кажется, делает объекты слишком изменчивыми для того, что я делаю (например, возьмите объект текстуры, изменение содержимого после создания - это нормально, но изменение его фундаментальных аспектов, таких как size, bitdept и т.д., нарушит множество существующих материалов c ++, которые предполагают, что такого рода вещи исправлены).Если я не реализую это, это просто остановит людей, вызывающих __init__ ПОСЛЕ его создания (или, по крайней мере, проигнорирует вызов, как это делает tuple).Или у меня должен быть какой-то флаг, который выдает исключение или что-то еще, если tp_init вызывается более одного раза для одного и того же объекта?
Кроме того, я думаю, что с большинством остальных я разобрался.
extern "C"
{
//creation + destruction
PyObject* global_alloc(PyTypeObject *type, Py_ssize_t items)
{
return (PyObject*)new char[type->tp_basicsize + items*type->tp_itemsize];
}
void global_free(void *mem)
{
delete[] (char*)mem;
}
}
template<class T> class ExtensionType
{
PyTypeObject *t;
ExtensionType()
{
t = new PyTypeObject();//not sure on this one, what is the "correct" way to create an empty type object
memset((void*)t, 0, sizeof(PyTypeObject));
static PyVarObject init = {PyObject_HEAD_INIT, 0};
*((PyObject*)t) = init;
t->tp_basicsize = sizeof(T);
t->tp_itemsize = 0;
t->tp_name = "unknown";
t->tp_alloc = (allocfunc) global_alloc;
t->tp_free = (freefunc) global_free;
t->tp_new = (newfunc) T::obj_new;
t->tp_dealloc = (destructor)T::obj_dealloc;
...
}
...bunch of methods for changing stuff...
PyObject *Finalise()
{
...
}
};
template <class T> PyObjectExtension : public PyObject
{
...
extern "C" static PyObject* obj_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
{
void *mem = (void*)subtype->tp_alloc(subtype, 0);
return (PyObject*)new(mem) T(args, kwds)
}
extern "C" static void obj_dealloc(PyObject *obj)
{
~T();
obj->ob_type->tp_free(obj);//most of the time this is global_free(obj)
}
...
};
class MyObject : PyObjectExtension<MyObject>
{
public:
static PyObject* InitType()
{
ExtensionType<MyObject> extType();
...sets other stuff...
return extType.Finalise();
}
...
};
Решение
Документация для них находится по адресу http://docs.python.org/3.0/c-api/typeobj.html и http://docs.python.org/3.0/extending/newtypes.html описывает, как создать свой собственный тип.
tp_alloc выполняет низкоуровневое выделение памяти для экземпляра.Это эквивалентно malloc(), плюс инициализируйте refcnt равным 1.Python имеет свой собственный распределитель, PyType_GenericAlloc, но тип может реализовать специализированный распределитель.
tp_new - это то же самое, что __new__ в Python.Обычно он используется для неизменяемых объектов, где данные хранятся в самом экземпляре, по сравнению с указателем на данные.Например, строки и кортежи хранят свои данные в экземпляре, вместо того чтобы использовать char * или PyTuple *.
В этом случае tp_new вычисляет, сколько памяти требуется, на основе входных параметров, и вызывает tp_alloc для получения памяти, затем инициализирует необходимые поля.tp_new не нужно вызывать tp_alloc.Он может, например, возвращать кэшированный объект.
tp_init - это то же самое, что __init__ в Python.Большая часть вашей инициализации должна быть в этой функции.
Различие между __new__ и __init__ называется двухэтапная инициализация, или двухфазная инициализация.
Ты говоришь "c ++ просто имеет новые" но это неверно.tp_alloc соответствует пользовательскому распределителю arena в C ++, __new__ соответствует распределителю пользовательского типа (заводской функции), а __init__ больше похож на конструктор.В этой последней ссылке больше обсуждается параллель между стилем C ++ и Python.
Также читайте http://www.python.org/download/releases/2.2/descrintro/ подробнее о том, как взаимодействуют __new__ и __init__.
Вы пишете, что хотите "создать объект непосредственно на c ++".Это довольно сложно, потому что, по крайней мере, вам придется преобразовать любые исключения Python, возникшие во время создания экземпляра объекта, в исключение C ++.Вы могли бы попробовать обратиться к Boost::Python за некоторой помощью с этой задачей.Или вы можете использовать двухфазную инициализацию.;)
Другие советы
Я вообще не знаю API python, но если python разделяет выделение и инициализацию, вы должны иметь возможность использовать placement new .
например ,:
// tp_alloc
void *buffer = new char[sizeof(MyExtensionObject)];
// tp_init or tp_new (not sure what the distinction is there)
new (buffer) MyExtensionObject(args);
return static_cast<MyExtensionObject*>(buffer);
...
// tp_del
myExtensionObject->~MyExtensionObject(); // call dtor
// tp_dealloc (or tp_free? again I don't know the python apis)
delete [] (static_cast<char*>(static_cast<void*>(myExtensionObject)));