Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define class methods in Python C API?

I was trying to create a Python class definition in C++ code and access it in Python. However, the function is called but the parameters are not received correctly. Please help me in doing this properly.

#include <iostream>
#include <Python.h>

using namespace std;

#include <Python.h>

static PyObject* MyClass__Init(PyObject *self, PyObject *args)
{
    cout << "MyClass__Init Called" << endl;
    Py_INCREF(Py_None);
    return Py_None;
};

static PyObject* MyModule__Start(PyObject *self, PyObject *args)
{
    const char* zBuff;

    if (PyArg_ParseTuple(args, "s", &zBuff))
        cout << "MyModule Start Called with parameter " << zBuff << endl;
    else
        cout << "MyModule Start ERROR" << endl;

    Py_INCREF(Py_None);
    return Py_None;
};

static PyObject* MyClass__Start(PyObject *self, PyObject *args)
{
    const char* zBuff;

    if (PyArg_ParseTuple(args, "s", &zBuff))
        cout << "MyClass Start Called with parameter" << zBuff << endl;
    else
        cout << "MyClass Start ERROR" << endl;

    Py_INCREF(Py_None);
    return Py_None;
};

static PyMethodDef pModuleMethods[] =
{
    {"Start", MyModule__Start, METH_VARARGS, ""},
    {NULL, NULL, 0, NULL}
};

static PyMethodDef pClassMethods[] = 
{
    {"__init__", MyClass__Init, METH_VARARGS, ""},
    {"Start", MyClass__Start, METH_VARARGS, ""},
    {NULL, NULL, 0, NULL}
};

void Start()
{
    Py_Initialize();

    /* create a new module and class */
    PyObject *pClassDic = PyDict_New();
    PyObject *pClassName = PyString_FromString("MyClass");
    PyObject *pClass = PyClass_New(NULL, pClassDic, pClassName);

    PyObject *pModule = Py_InitModule("MyModule", pModuleMethods);
    PyObject *pModuleDic = PyModule_GetDict(pModule);

    /* add methods to class */
    for (PyMethodDef* pDef = pClassMethods; pDef->ml_name != NULL; pDef++)
    {
        PyObject *pFunc = PyCFunction_New(pDef, NULL);
        PyObject *pMethod = PyMethod_New(pFunc, NULL, pClass);
        PyDict_SetItemString(pClassDic, pDef->ml_name, pMethod);
    }

    PyDict_SetItemString(pModuleDic, "MyClass", pClass);

    PyRun_SimpleString("import MyModule\n"
        "MyModule.Start('Hello Module')\n"
        "myObj = MyModule.MyClass()\n"
        "myObj.Start('Hello Class')\n");

    Py_Finalize();
};

int main()
{
    Start();
};

Output is,

MyModule Start Called with parameter Hello Module
MyClass__Init Called
MyClass Start ERROR

The Module function is called without any issue, but the class method is called without the proper input variable.

like image 991
Sujith Gunawardhane Avatar asked Nov 25 '25 15:11

Sujith Gunawardhane


1 Answers

It appears that the parameter to self is always NULL and instead - for class methods - the reference to self is passed within the argument list. So, to parse the arguments of a class method you need to parse the reference to self, too.

Since Python 2.6 you can provide a list of format specifiers to PyArg_ParseTuple. The number of format specifiers has to fit the number of arguments that are passed to the function (see https://docs.python.org/2/c-api/arg.html under (items) (tuple) [matching-items]).

By modifying your MyClass__Start function to parse an additional parameter, you are able to parse and print both arguments to inspect them. For me, the following code

static PyObject* MyClass__Start(PyObject *self, PyObject *args)
{
    PyObject* argListSelf;
    const char* zBuff;

    if (PyArg_ParseTuple(args, "Os", &argListSelf, &zBuff)) {
        cout << "MyClass Start Called with parameters " <<  
        cout << PyString_AsString(PyObject_Str(argListSelf)) <<
        cout << " and " << zBuff << endl;

        cout << "self " << PyString_AsString(PyObject_Str(self)) << endl;
    }   
    else {
        if(PyErr_Occurred())
            PyErr_Print();

        cout << "MyClass Start ERROR" << endl;
    }   

    Py_INCREF(Py_None);
    return Py_None;
};

results in

MyModule Start Called with parameter Hello Module
MyClass__Init Called
MyClass Start Called with parameters \
    0x602428<?.MyClass instance at 0x7f484a333200>0x602428 and HelloClass
self <NULL>

Note that I printed the pointer value of self, which is NULL. I also added

if(PyErr_Occurred())
    PyErr_Print();

which I would always add for debugging purposes.

like image 154
bbastu Avatar answered Nov 28 '25 04:11

bbastu



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!