具有完整C++类互操作的最小可运行单继承示例
使用C定义继承Python类是“容易”的,因为它在https://docs.python.org/3/extending/newtypes_tutorial.html#subclassing-other-types上有更多或更少的文档。这里有一个示例,将所有这些片段放入单个可编译文件中,并使用显式定义的基类,而不是PyListObject
:userland/libs/python_embed/pure.c
。
更难并且没有任何示例的是具有完整C++类对象继承示例,其中您必须将数据放入C++对象中,以便方法调用正常工作。
最烦人的部分是有两个Python PyTypeObject
对象,每个类一个,但是C++数据必须同时分配。我找到的唯一解决方案是在派生类上使用new
,如果从Derived类初始化,则不进行分配。
也许以下内容是正确的。它通过了我的简单测试,但它是否泄漏内存?谁知道!
编译并运行测试:
g++ -std=c++11 -ggdb3 -O0 -pedantic-errors -Wall -Wextra -Wno-missing-field-initializers $(pkg-config --cflags python3) -o 'pure_cpp.out' 'pure_cpp.cpp' $(pkg-config --libs python3)
[ "$(PYTHONPATH="${PYTHONPATH:-}:." ./pure_cpp.out pure test_native_class)" = 13 ]
pure.py
import my_native_module
def test_native_class():
my_native_object = my_native_module.MyNativeClass('ab', 'cd', 13)
assert my_native_object.first == 'ab'
assert my_native_object.last == 'cd'
assert my_native_object.number == 13
assert my_native_object.name() == 'ab cd 14'
my_native_object = my_native_module.MyNativeClass(first='ef', last='gh', number=13)
assert my_native_object.name() == 'ef gh 14'
my_native_object = my_native_module.MyNativeClass()
my_native_object.first = 'ih'
assert my_native_object.name() == 'ih 1'
my_derived_native_object = my_native_module.MyDerivedNativeClass('ab', 'cd', 13, 'ef', 'gh', 21)
assert my_derived_native_object.name() == 'ab cd 14'
assert my_derived_native_object.name2() == 'ab cd 14 ef gh 23'
return 13
pure_cpp.cpp
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"
#include <string>
#include <sstream>
struct MyNativeClass {
std::string first;
std::string last;
int number;
MyNativeClass() : number(0) {};
MyNativeClass(
const std::string& first,
const std::string& last,
int number
) : first(first), last(last), number(number) {}
std::string name() {
std::stringstream ss;
ss << first << " " << last << " " << number + 1;
return ss.str();
}
};
struct MyDerivedNativeClass : public MyNativeClass {
std::string first2;
std::string last2;
int number2;
MyDerivedNativeClass() {};
MyDerivedNativeClass(
const std::string& first,
const std::string& last,
int number,
const std::string& first2,
const std::string& last2,
int number2
) : MyNativeClass(first, last, number),
first2(first2), last2(last2), number2(number2) {}
std::string name2() {
std::stringstream ss;
ss << first << " " << last << " " << number + 1 << " "
<< first2 << " " << last2 << " " << number2 + 2;
return ss.str();
}
};
typedef struct {
PyObject_HEAD
MyNativeClass *cpp_object;
} my_native_module_MyNativeClass;
static void
my_native_module_MyNativeClass_dealloc(my_native_module_MyNativeClass *self) {
delete self->cpp_object;
Py_TYPE(self)->tp_free((PyObject *) self);
}
static PyObject *
my_native_module_MyNativeClass_new_noalloc(my_native_module_MyNativeClass *self, PyObject *args, PyObject *kwds) {
(void)args;
(void)kwds;
if (self != NULL) {
if (self->cpp_object == NULL) {
self->cpp_object = new MyNativeClass();
}
}
return (PyObject *) self;
}
static PyObject *
my_native_module_MyNativeClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
my_native_module_MyNativeClass *self;
self = (my_native_module_MyNativeClass *) type->tp_alloc(type, 0);
return (PyObject *) my_native_module_MyNativeClass_new_noalloc(self, args, kwds);
}
static int
my_native_module_MyNativeClass_init(my_native_module_MyNativeClass *self, PyObject *args, PyObject *kwds)
{
static const char *kwlist[] = {"first", "last", "number", NULL};
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", (char**)kwlist,
&first, &last, &self->cpp_object->number))
return -1;
if (first) {
if (!PyUnicode_Check(first)) {
PyErr_SetString(PyExc_TypeError, "The first attribute value must be a string");
return -1;
}
self->cpp_object->first = PyBytes_AsString(PyUnicode_AsASCIIString(first));
}
if (last) {
if (!PyUnicode_Check(last)) {
PyErr_SetString(PyExc_TypeError, "The last attribute value must be a string");
return -1;
}
self->cpp_object->last = PyBytes_AsString(PyUnicode_AsASCIIString(last));
}
return 0;
}
static PyObject *
my_native_module_MyNativeClass_getfirst(my_native_module_MyNativeClass *self, void *closure)
{
(void)closure;
return PyUnicode_FromString(self->cpp_object->first.c_str());
}
static int
my_native_module_MyNativeClass_setfirst(my_native_module_MyNativeClass *self, PyObject *value, void *closure)
{
(void)closure;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first attribute value must be a string");
return -1;
}
self->cpp_object->first = PyBytes_AsString(PyUnicode_AsASCIIString(value));
return 0;
}
static PyObject *
my_native_module_MyNativeClass_getlast(my_native_module_MyNativeClass *self, void *closure)
{
(void)closure;
return PyUnicode_FromString(self->cpp_object->last.c_str());
}
static int
my_native_module_MyNativeClass_setlast(my_native_module_MyNativeClass *self, PyObject *value, void *closure)
{
(void)closure;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The last attribute value must be a string");
return -1;
}
self->cpp_object->last = PyBytes_AsString(PyUnicode_AsASCIIString(value));
return 0;
}
static PyObject *
my_native_module_MyNativeClass_getnumber(my_native_module_MyNativeClass *self, void *closure)
{
(void)closure;
return PyLong_FromLong(self->cpp_object->number);
}
static int
my_native_module_MyNativeClass_setnumber(my_native_module_MyNativeClass *self, PyObject *value, void *closure)
{
(void)closure;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the number attribute");
return -1;
}
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The number attribute value must be a string");
return -1;
}
self->cpp_object->number = PyLong_AsLong(value);
return 0;
}
static PyObject *
my_native_module_MyNativeClass_name(my_native_module_MyNativeClass *self, PyObject *Py_UNUSED(ignored))
{
return PyUnicode_FromString(self->cpp_object->name().c_str());
}
static PyMethodDef my_native_module_MyNativeClass_methods[] = {
{
"name",
(PyCFunction)my_native_module_MyNativeClass_name,
METH_NOARGS,
"Return the name, combining the first and last name"
},
{NULL}
};
static PyGetSetDef my_native_module_MyNativeClass_getsetters[] = {
{(char *)"first", (getter) my_native_module_MyNativeClass_getfirst, (setter) my_native_module_MyNativeClass_setfirst,
(char *)"first name", NULL},
{(char *)"last", (getter) my_native_module_MyNativeClass_getlast, (setter) my_native_module_MyNativeClass_setlast,
(char *)"last name", NULL},
{(char *)"number", (getter) my_native_module_MyNativeClass_getnumber, (setter) my_native_module_MyNativeClass_setnumber,
(char *)"number", NULL},
{NULL}
};
static PyTypeObject my_native_module_MyNativeClassType = {
PyVarObject_HEAD_INIT(NULL, 0)
};
typedef struct {
my_native_module_MyNativeClass base;
MyDerivedNativeClass *cpp_object;
} my_native_module_MyDerivedNativeClass;
static void
my_native_module_MyDerivedNativeClass_dealloc(my_native_module_MyDerivedNativeClass *self) {
Py_TYPE(self)->tp_base->tp_dealloc((PyObject *) self);
}
static PyObject *
my_native_module_MyDerivedNativeClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
(void)args;
(void)kwds;
my_native_module_MyDerivedNativeClass *self;
self = (my_native_module_MyDerivedNativeClass *) type->tp_alloc(type, 0);
self->cpp_object = new MyDerivedNativeClass();
self->base.cpp_object = self->cpp_object;
my_native_module_MyNativeClass_new_noalloc((my_native_module_MyNativeClass *) self, args, kwds);
return (PyObject *) self;
}
static int
my_native_module_MyDerivedNativeClass_init(my_native_module_MyDerivedNativeClass *self, PyObject *args, PyObject *kwds)
{
static const char *kwlist[] = {"first", "last", "number", "first2", "last2", "number2", NULL};
PyObject *first = NULL, *last = NULL, *first2 = NULL, *last2 = NULL, *tmp;
int ret;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOi", (char**)kwlist,
&first, &last, &tmp, &first2, &last2, &self->cpp_object->number2))
return -1;
PyObject *base_args = PySequence_GetSlice(args, 0, 3);
ret = my_native_module_MyNativeClassType.tp_init((PyObject *) self, base_args, kwds);
Py_DECREF(base_args);
if (ret < 0)
return -1;
if (first2) {
if (!PyUnicode_Check(first2)) {
PyErr_SetString(PyExc_TypeError, "The first attribute value must be a string");
return -1;
}
self->cpp_object->first2 = PyBytes_AsString(PyUnicode_AsASCIIString(first2));
}
if (last2) {
if (!PyUnicode_Check(last2)) {
PyErr_SetString(PyExc_TypeError, "The last2 attribute value must be a string");
return -1;
}
self->cpp_object->last2 = PyBytes_AsString(PyUnicode_AsASCIIString(last2));
}
return 0;
}
static PyObject *
my_native_module_MyDerivedNativeClass_getfirst2(my_native_module_MyDerivedNativeClass *self, void *closure)
{
(void)closure;
return PyUnicode_FromString(self->base.cpp_object->first.c_str());
}
static int
my_native_module_MyDerivedNativeClass_setfirst2(my_native_module_MyDerivedNativeClass *self, PyObject *value, void *closure)
{
(void)closure;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the first2 attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The first2 attribute value must be a string");
return -1;
}
self->base.cpp_object->first = PyBytes_AsString(PyUnicode_AsASCIIString(value));
return 0;
}
static PyObject *
my_native_module_MyDerivedNativeClass_getlast2(my_native_module_MyDerivedNativeClass *self, void *closure)
{
(void)closure;
return PyUnicode_FromString(self->base.cpp_object->last.c_str());
}
static int
my_native_module_MyDerivedNativeClass_setlast2(my_native_module_MyDerivedNativeClass *self, PyObject *value, void *closure)
{
(void)closure;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the last2 attribute");
return -1;
}
if (!PyUnicode_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The last2 attribute value must be a string");
return -1;
}
self->base.cpp_object->last = PyBytes_AsString(PyUnicode_AsASCIIString(value));
return 0;
}
static PyObject *
my_native_module_MyDerivedNativeClass_getnumber2(my_native_module_MyDerivedNativeClass *self, void *closure)
{
(void)closure;
return PyLong_FromLong(self->base.cpp_object->number);
}
static int
my_native_module_MyDerivedNativeClass_setnumber2(my_native_module_MyDerivedNativeClass *self, PyObject *value, void *closure)
{
(void)closure;
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete the number2 attribute");
return -1;
}
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"The number2 attribute value must be a string");
return -1;
}
self->base.cpp_object->number = PyLong_AsLong(value);
return 0;
}
static PyObject *
my_native_module_MyDerivedNativeClass_name2(my_native_module_MyDerivedNativeClass *self, PyObject *Py_UNUSED(ignored))
{
return PyUnicode_FromString(self->cpp_object->name2().c_str());
}
static PyGetSetDef my_native_module_MyDerivedNativeClass_getsetters[] = {
{(char *)"first2", (getter) my_native_module_MyDerivedNativeClass_getfirst2, (setter) my_native_module_MyDerivedNativeClass_setfirst2,
(char *)"first2 name", NULL},
{(char *)"last2", (getter) my_native_module_MyDerivedNativeClass_getlast2, (setter) my_native_module_MyDerivedNativeClass_setlast2,
(char *)"last2 name", NULL},
{(char *)"number2", (getter) my_native_module_MyDerivedNativeClass_getnumber2, (setter) my_native_module_MyDerivedNativeClass_setnumber2,
(char *)"number2", NULL},
{NULL}
};
static PyMethodDef my_native_module_MyDerivedNativeClass_methods[] = {
{
"name2",
(PyCFunction)my_native_module_MyDerivedNativeClass_name2,
METH_NOARGS,
"Return the name2, combining the first2 and last2 name2"
},
{NULL}
};
static PyTypeObject my_native_module_MyDerivedNativeClassType = {
PyVarObject_HEAD_INIT(NULL, 0)
};
static PyModuleDef my_native_module = {
PyModuleDef_HEAD_INIT,
"my_native_module",
"My native module",
-1,
};
PyMODINIT_FUNC
my_native_module_init_func(void) {
PyObject *m;
m = PyModule_Create(&my_native_module);
if (m == NULL)
return NULL;
if (PyType_Ready(&my_native_module_MyNativeClassType) < 0)
return NULL;
Py_INCREF(&my_native_module_MyNativeClassType);
if (PyModule_AddObject(m, "MyNativeClass", (PyObject *) &my_native_module_MyNativeClassType) < 0) {
Py_DECREF(&my_native_module_MyNativeClassType);
Py_DECREF(m);
return NULL;
}
my_native_module_MyDerivedNativeClassType.tp_base = &my_native_module_MyNativeClassType;
if (PyType_Ready(&my_native_module_MyDerivedNativeClassType) < 0)
return NULL;
Py_INCREF(&my_native_module_MyDerivedNativeClassType);
if (PyModule_AddObject(m, "MyDerivedNativeClass", (PyObject *) &my_native_module_MyDerivedNativeClassType) < 0) {
Py_DECREF(&my_native_module_MyDerivedNativeClassType);
Py_DECREF(m);
return NULL;
}
return m;
}
int
main(int argc, char *argv[])
{
PyObject *pName, *pModule, *pFunc;
PyObject *pArgs, *pValue;
int i;
if (argc < 3) {
fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
return 1;
}
my_native_module_MyNativeClassType.tp_name = "my_native_module.MyNativeClass";
my_native_module_MyNativeClassType.tp_doc = "My native class";
my_native_module_MyNativeClassType.tp_basicsize = sizeof(my_native_module_MyNativeClass);
my_native_module_MyNativeClassType.tp_itemsize = 0;
my_native_module_MyNativeClassType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC;
my_native_module_MyNativeClassType.tp_new = my_native_module_MyNativeClass_new;
my_native_module_MyNativeClassType.tp_init = (initproc) my_native_module_MyNativeClass_init;
my_native_module_MyNativeClassType.tp_dealloc = (destructor) my_native_module_MyNativeClass_dealloc;
my_native_module_MyNativeClassType.tp_methods = my_native_module_MyNativeClass_methods;
my_native_module_MyNativeClassType.tp_getset = my_native_module_MyNativeClass_getsetters;
my_native_module_MyDerivedNativeClassType.tp_name = "my_native_module.MyDerivedNativeClass";
my_native_module_MyDerivedNativeClassType.tp_doc = "My native class";
my_native_module_MyDerivedNativeClassType.tp_basicsize = sizeof(my_native_module_MyDerivedNativeClass);
my_native_module_MyDerivedNativeClassType.tp_itemsize = 0;
my_native_module_MyDerivedNativeClassType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC;
my_native_module_MyDerivedNativeClassType.tp_new = my_native_module_MyDerivedNativeClass_new;
my_native_module_MyDerivedNativeClassType.tp_init = (initproc) my_native_module_MyDerivedNativeClass_init;
my_native_module_MyDerivedNativeClassType.tp_dealloc = (destructor) my_native_module_MyDerivedNativeClass_dealloc;
my_native_module_MyDerivedNativeClassType.tp_methods = my_native_module_MyDerivedNativeClass_methods;
my_native_module_MyDerivedNativeClassType.tp_getset = my_native_module_MyDerivedNativeClass_getsetters;
PyImport_AppendInittab("my_native_module", &my_native_module_init_func);
Py_Initialize();
pName = PyUnicode_DecodeFSDefault(argv[1]);
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, argv[2]);
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(argc - 3);
for (i = 0; i < argc - 3; ++i) {
pValue = PyLong_FromLong(atoi(argv[i + 3]));
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
return 1;
}
PyTuple_SetItem(pArgs, i, pValue);
}
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
printf("%ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);
} else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr,"Call failed\n");
return 1;
}
} else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
} else {
PyErr_Print();
fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
return 1;
}
if (Py_FinalizeEx() < 0) {
return 120;
}
return 0;
}
GitHub上游。
您可以使用pybind11或其他库以约10倍的代码量(但可能需要更长的编译时间)获得类似的结果,例如: 从Python调用C / C ++?
在Ubuntu 18.04、Python 3.6.8和GCC 7.4.0中进行了测试。