cpython-3.xcpythonpython-c-api

Create instance of a imported python class with C API


I want to create an instance of a Python class with the C API.

My specific use case is the PySerial Object. My Python code would be:

import serial
test = serial.Serial()

I couldn't find any clue or Tutorial for doing so. The only thing I think to know is to declare it as a dependency in my pyproject.toml and import it somehow with a PyImport_* function.

My current code looks somehow like this:

...
/*! Python Module Initialization Function
 * This function will be called when importing the Module to the Python Interpreter.
 * In this function all Python Objects and Constants are added to the Python Module.
 */
PyMODINIT_FUNC PyInit_Module(void)
{
    PyObject *m;
    // Check SubClass Type
    if (PyType_Ready(&SubClassType) < 0) return NULL;

    m = PyModule_Create(&Module_module);
    if (m == NULL) return NULL;

    /* Adding SubClass Class */
    Py_INCREF(&SubClassType);

    if (PyModule_AddObject(m, "SubClass", (PyObject *)&SubClassType) < 0) {
        Py_DECREF(&SubClassType);
        Py_DECREF(m);
        return NULL;
    }

    PySerialObject = PyImport_ImportModule("serial");
    if (PySerialObject == NULL) {
        Py_DECREF(&SubClassType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}
...
/*! SubClass new
 * Creating new SubClass Python Object
 */
static PyObject *SubClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    SubClassObject *self;
    self = (SubClassObject *)type->tp_alloc(type, 0);
    self->serial = NULL;
    if (self != NULL) {
                //self->serial = PyObject_CallNoArgs((PyObject*)Py_TYPE(PySerialObject));
        self->serial = PyObject_CallOneArg((PyObject *)Py_TYPE(PySerialObject), Py_BuildValue("serial.Serial()"));
        if (self->serial == NULL) return NULL;
    }
    return (PyObject *)self;
}

My goal is to use the serial.Serial() Python object inside my C code.


Solution

  • I found my answer working for my with guidance from the answer Any way to create a NumPy matrix with C API?

    The clue is to first import the module and then get the Object from their attributes. With this Object you can instantiate more Instances. In code it would look something like:

    /*! Python Module Initialization Function
     * This function will be called when importing the Module to the Python Interpreter.
     * In this function all Python Objects and Constants are added to the Python Module.
     */
    PyMODINIT_FUNC PyInit_Module(void)
    {
        PyObject *m;
        // Check SubClass Type
        if (PyType_Ready(&SubClassType) < 0) return NULL;
    
        m = PyModule_Create(&Module_module);
        if (m == NULL) return NULL;
    
        /* Adding SubClass Class */
        Py_INCREF(&SubClassType);
    
        if (PyModule_AddObject(m, "SubClass", (PyObject *)&SubClassType) < 0) {
            Py_DECREF(&SubClassType);
            Py_DECREF(m);
            return NULL;
        }
    
        PySerial = PyImport_ImportModule("serial");
        if (PySerial == NULL) {
            Py_DECREF(&SubClassType);
            Py_DECREF(m);
            return NULL;
        }
        PySerialObject = PyObject_GetAttrString(PySerial, "Serial");
        if (PySerialObject == NULL) {
            Py_DECREF(&SubClassType);
            Py_DECREF(&PySerial);
            Py_DECREF(m);
            return NULL;
        }
    
        return m;
    }
    ...
    /*! SubClass new
     * Creating new SubClass Python Object
     */
    static PyObject *SubClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        SubClassObject *self;
        self = (SubClassObject *)type->tp_alloc(type, 0);
        if (self == NULL) return NULL;
        self->serial = NULL;
        self->serial = PyObject_CallNoArgs(PySerialObject);
        if (self->serial == NULL) return NULL;
        return (PyObject *)self;
    }
    

    For a minimal solution I would propose

    PyObject *serial = PyImport_ImportModule("serial");
    PyObject *serialObject = PyObject_GetAttrString(serial, "Serial");
    PyObject *my_serial = PyObject_CallNoArgs(serialObject);
    

    But of course you can use any of the PyObject_Call* functions.

    I want to leave it here so future developers have an easy time searching for this exact problem.