为 Python 写一个 C++ 扩展模块
Python 扩展
![](/data/attachment/album/202301/02/173501o26htajatlpj0lqt.jpg)



> 
> 使用 C 扩展为 Python 提供特定功能。
> 
> 
> 


在前一篇文章中，我介绍了 [六个 Python 解释器](https://opensource.com/article/22/9/python-interpreters-2022)。在大多数系统上，CPython 是默认的解释器，而且根据民意调查显示，它还是最流行的解释器。Cpython 的独有功能是使用扩展 API 用 C 语言编写 Python 模块。用 C 语言编写 Python 模块允许你将计算密集型代码转移到 C，同时保留 Python 的易用性。


在本文中，我将向你展示如何编写一个 C++ 扩展模块。使用 C++ 而不是 C，因为大多数编译器通常都能理解这两种语言。我必须提前说明缺点：以这种方式构建的 Python 模块不能移植到其他解释器中。它们只与 CPython 解释器配合工作。因此，如果你正在寻找一种可移植性更好的与 C 语言模块交互的方式，考虑下使用 [ctypes](https://docs.python.org/3/library/ctypes.html#module-ctypes) 模块。


### 源代码


和往常一样，你可以在 [GitHub](https://github.com/hANSIc99/PythonCppExtension) 上找到相关的源代码。仓库中的 C++ 文件有以下用途：


* `my_py_module.cpp`: Python 模块 `MyModule` 的定义
* `my_cpp_class.h`: 一个头文件 - 只有一个暴露给 Python 的 C++ 类
* `my_class_py_type.h/cpp`: Python 形式的 C++ 类
* `pydbg.cpp`: 用于调试的单独应用程序


本文构建的 Python 模块不会有任何实际用途，但它是一个很好的示例。


### 构建模块


在查看源代码之前，你可以检查它是否能在你的系统上编译。[我使用 CMake](https://opensource.com/article/21/5/cmake) 来创建构建的配置信息，因此你的系统上必须安装 CMake。为了配置和构建这个模块，可以让 Python 去执行这个过程：



```
$ python3 setup.py build

```

或者手动执行：



```
$ cmake -B build
$ cmake --build build

```

之后，在 `/build` 子目录下你会有一个名为 `MyModule. so` 的文件。


### 定义扩展模块


首先，看一下 `my_py_module.cpp` 文件，尤其是 `PyInit_MyModule` 函数：



```
PyMODINIT_FUNC
PyInit_MyModule(void) {
    PyObject* module = PyModule_Create(&my_module);
    
    PyObject *myclass = PyType_FromSpec(&spec_myclass);
    if (myclass == NULL){
        return NULL;
    }
    Py_INCREF(myclass);
    
    if(PyModule_AddObject(module, "MyClass", myclass) < 0){
        Py_DECREF(myclass);
        Py_DECREF(module);
        return NULL;
    }
    return module;
}

```

这是本例中最重要的代码，因为它是 CPython 的入口点。一般来说，当一个 Python C 扩展被编译并作为共享对象二进制文件提供时，CPython 会在同名二进制文件中（`<ModuleName>.so`）搜索 `PyInit_<ModuleName>` 函数，并在试图导入时执行它。


无论是声明还是实例，所有 Python 类型都是 [PyObject](https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pyobject#c.PyObject) 的一个指针。在此函数的第一部分中，`module` 通过 `PyModule_Create(...)` 创建的。正如你在 `module` 详述（`my_py_module`，同名文件）中看到的，它没有任何特殊的功能。


之后，调用 [PyType\_FromSpec](https://docs.python.org/3/c-api/type.html#c.PyType_FromSpec) 为自定义类型 `MyClass` 创建一个 Python [堆类型](https://docs.python.org/3/c-api/typeobj.html#heap-types) 定义。一个堆类型对应于一个 Python 类，然后将它赋值给 `MyModule` 模块。


*注意，如果其中一个函数返回失败，则必须减少以前创建的复制对象的引用计数，以便解释器删除它们。*


### 指定 Python 类型


`MyClass` 详述在 [my\_class\_py\_type.h](https://github.com/hANSIc99/PythonCppExtension/blob/main/my_class_py_type.h) 中可以找到，它作为 [PyType\_Spec](https://docs.python.org/3/c-api/type.html#c.PyType_Spec) 的一个实例：



```
static PyType_Spec spec_myclass = {
    "MyClass",                                  // name
    sizeof(MyClassObject) + sizeof(MyClass),    // basicsize
    0,                                          // itemsize
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   // flags
    MyClass_slots                               // slots
};

```

它定义了一些基本类型信息，它的大小包括 Python 表示的大小（`MyClassObject`）和普通 C++ 类的大小（`MyClass`）。`MyClassObject` 定义如下：



```
typedef struct {
    PyObject_HEAD
    int         m_value;
    MyClass*    m_myclass;
} MyClassObject;

```

Python 表示的话就是 [PyObject](https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pyobject#c.PyObject) 类型，由 `PyObject_HEAD` 宏和其他一些成员定义。成员 `m_value` 视为普通类成员，而成员 `m_myclass` 只能在 C++ 代码内部访问。


[PyType\_Slot](https://docs.python.org/release/3.9.1/c-api/type.html?highlight=pytype_slot#c.PyType_Slot) 定义了一些其他功能：



```
static PyType_Slot MyClass_slots[] = {
    {Py_tp_new,     (void*)MyClass_new},
    {Py_tp_init,    (void*)MyClass_init},
    {Py_tp_dealloc, (void*)MyClass_Dealloc},
    {Py_tp_members, MyClass_members},
    {Py_tp_methods, MyClass_methods},
    {0, 0} /* Sentinel */
};

```

在这里，设置了一些初始化和析构函数的跳转，还有普通的类方法和成员，还可以设置其他功能，如分配初始属性字典，但这是可选的。这些定义通常以一个哨兵结束，包含 `NULL` 值。


要完成类型详述，还包括下面的方法和成员表：



```
static PyMethodDef MyClass_methods[] = {
    {"addOne", (PyCFunction)MyClass_addOne, METH_NOARGS,  PyDoc_STR("Return an incrmented integer")},
    {NULL, NULL} /* Sentinel */
};

static struct PyMemberDef MyClass_members[] = {
    {"value", T_INT, offsetof(MyClassObject, m_value)},
    {NULL} /* Sentinel */
};

```

在方法表中，定义了 Python 方法 `addOne`，它指向相关的 C++ 函数 `MyClass_addOne`。它充当了一个包装器，它在 C++ 类中调用 `addOne()` 方法。


在成员表中，只有一个为演示目的而定义的成员。不幸的是，在 [PyMemberDef](https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pymemberdef#c.PyMemberDef) 中使用的 [offsetof](https://en.cppreference.com/w/cpp/types/offsetof) 不允许添加 C++ 类型到 `MyClassObject`。如果你试图放置一些 C++ 类型的容器（如 [std::optional](https://en.cppreference.com/w/cpp/utility/optional)），编译器会抱怨一些内存布局相关的警告。


### 初始化和析构


`MyClass_new` 方法只为 `MyClassObject` 提供一些初始值，并为其类型分配内存：



```
PyObject *MyClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds){
    std::cout << "MtClass_new() called!" << std::endl;

    MyClassObject *self;
    self = (MyClassObject*) type->tp_alloc(type, 0);
    if(self != NULL){ // -> 分配成功
        // 赋初始值
        self->m_value   = 0;
        self->m_myclass = NULL; 
    }
    return (PyObject*) self;
}

```

实际的初始化发生在 `MyClass_init` 中，它对应于 Python 中的 [\_\_init\_\_()](https://docs.python.org/3/library/dataclasses.html?highlight=__init__) 方法：



```
int MyClass_init(PyObject *self, PyObject *args, PyObject *kwds){
    
    ((MyClassObject *)self)->m_value = 123;
    
    MyClassObject* m = (MyClassObject*)self;
    m->m_myclass = (MyClass*)PyObject_Malloc(sizeof(MyClass));

    if(!m->m_myclass){
        PyErr_SetString(PyExc_RuntimeError, "Memory allocation failed");
        return -1;
    }

    try {
        new (m->m_myclass) MyClass();
    } catch (const std::exception& ex) {
        PyObject_Free(m->m_myclass);
        m->m_myclass = NULL;
        m->m_value   = 0;
        PyErr_SetString(PyExc_RuntimeError, ex.what());
        return -1;
    } catch(...) {
        PyObject_Free(m->m_myclass);
        m->m_myclass = NULL;
        m->m_value   = 0;
        PyErr_SetString(PyExc_RuntimeError, "Initialization failed");
        return -1;
    }

    return 0;
}

```

如果你想在初始化过程中传递参数，必须在此时调用 [PyArg\_ParseTuple](https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple)。简单起见，本例将忽略初始化过程中传递的所有参数。在函数的第一部分中，`PyObject` 指针（`self`）被强转为 `MyClassObject` 类型的指针，以便访问其他成员。此外，还分配了 C++ 类的内存，并执行了构造函数。


注意，为了防止内存泄漏，必须仔细执行异常处理和内存分配（还有释放）。当引用计数将为零时，`MyClass_dealloc` 函数负责释放所有相关的堆内存。在文档中有一个章节专门讲述关于 C 和 C++ 扩展的内存管理。


### 包装方法


从 Python 类中调用相关的 C++ 类方法很简单：



```
PyObject* MyClass_addOne(PyObject *self, PyObject *args){
    assert(self);

    MyClassObject* _self = reinterpret_cast<MyClassObject*>(self);
    unsigned long val = _self->m_myclass->addOne();
    return PyLong_FromUnsignedLong(val);
}

```

同样，`PyObject` 参数（`self`）被强转为 `MyClassObject` 类型以便访问 `m_myclass`，它指向 C++ 对应类实例的指针。有了这些信息，调用 `addOne()` 类方法，并且结果以 [Python 整数对象](https://docs.python.org/3/c-api/long.html) 返回。


### 3 种方法调试


出于调试目的，在调试配置中编译 CPython 解释器是很有价值的。详细描述参阅 [官方文档](https://docs.python.org/3/c-api/intro.html#debugging-builds)。只要下载了预安装的解释器的其他调试符号，就可以按照下面的步骤进行操作。


#### GNU 调试器


当然，老式的 [GNU 调试器（GDB）](https://opensource.com/article/21/3/debug-code-gdb) 也可以派上用场。源码中包含了一个 [gdbinit](https://github.com/hANSIc99/PythonCppExtension/blob/main/gdbinit) 文件，定义了一些选项和断点，另外还有一个 [gdb.sh](https://github.com/hANSIc99/PythonCppExtension/blob/main/gdb.sh) 脚本，它会创建一个调试构建并启动一个 GDB 会话：


![Gnu 调试器（GDB）对于 Python C 和 C++ 扩展非常有用](/data/attachment/album/202301/02/173519zsbmmmlmfbksttbo.png)


GDB 使用脚本文件 [main.py](https://github.com/hANSIc99/PythonCppExtension/blob/main/main.py) 调用 CPython 解释器，它允许你轻松定义你想要使用 Python 扩展模块执行的所有操作。


#### C++ 应用


另一种方法是将 CPython 解释器嵌入到一个单独的 C++ 应用程序中。可以在仓库的 [pydbg.cpp](https://github.com/hANSIc99/PythonCppExtension/blob/main/pydbg.cpp) 文件中找到：



```
int main(int argc, char *argv[], char *envp[])
{
    Py_SetProgramName(L"DbgPythonCppExtension");
    Py_Initialize();

    PyObject *pmodule = PyImport_ImportModule("MyModule");
    if (!pmodule) {
        PyErr_Print();
        std::cerr << "Failed to import module MyModule" << std::endl;
        return -1;
    }

    PyObject *myClassType = PyObject_GetAttrString(pmodule, "MyClass");
    if (!myClassType) {
        std::cerr << "Unable to get type MyClass from MyModule" << std::endl;
        return -1;
    }

    PyObject *myClassInstance = PyObject_CallObject(myClassType, NULL);

    if (!myClassInstance) {
        std::cerr << "Instantioation of MyClass failed" << std::endl;
        return -1;
    }

    Py_DecRef(myClassInstance); // invoke deallocation
    return 0;
}

```

使用 [高级接口](https://docs.python.org/3/extending/embedding.html#very-high-level-embedding)，可以导入扩展模块并对其执行操作。它允许你在本地 IDE 环境中进行调试，还能让你更好地控制传递或来自扩展模块的变量。


缺点是创建一个额外的应用程序的成本很高。


#### VSCode 和 VSCodium LLDB 扩展


使用像 [CodeLLDB](https://github.com/vadimcn/vscode-lldb) 这样的调试器扩展可能是最方便的调试选项。仓库包含了一些 VSCode/VSCodium 的配置文件，用于构建扩展，如 [task.json](https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/tasks.json)、[CMake Tools](https://github.com/microsoft/vscode-cmake-tools) 和调用调试器（[launch.json](https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/launch.json)）。这种方法结合了前面几种方法的优点：在图形 IDE 中调试，在 Python 脚本文件中定义操作，甚至在解释器提示符中动态定义操作。


![VSCodium 有一个集成的调试器。](/data/attachment/album/202301/02/173519c6idmm5m1inmim11.png)


### 用 C++ 扩展 Python


Python 的所有功能也可以从 C 或 C++ 扩展中获得。虽然用 Python 写代码通常认为是一件容易的事情，但用 C 或 C++ 扩展 Python 代码是一件痛苦的事情。另一方面，虽然原生 Python 代码比 C++ 慢，但 C 或 C++ 扩展可以将计算密集型任务提升到原生机器码的速度。


你还必须考虑 ABI 的使用。稳定的 ABI 提供了一种方法来保持旧版本 CPython 的向后兼容性，如 [文档](https://docs.python.org/3/c-api/stable.html) 所述。


最后，你必须自己权衡利弊。如果你决定使用 C 语言来扩展 Python 中的一些功能，你已经看到了如何实现它。




---


via: <https://opensource.com/article/22/11/extend-c-python>


作者：[Stephan Avenwedde](https://opensource.com/users/hansic99) 选题：[lkxed](https://github.com/lkxed) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
In a previous article, I gave an overview of [six Python interpreters](https://opensource.com/article/22/9/python-interpreters-2022). On most systems, the CPython interpreter is the default, and also the poll in my last article showed that CPython is the most popular one. Specific to CPython is the ability to write Python modules in C using CPythons extensions API. Writing Python modules in C allows you to move computation-intensive code to C while preserving the ease of access of Python.

In this article, I’ll show you how to write an extension module. Instead of plain C, I use C++ because most compilers usually understand both. I have to mention one major drawback in advance: Python modules built this way are not portable to other interpreters. They only work in conjunction with the CPython interpreter. So if you are looking for a more portable way of interacting with C libraries, consider using the [ctypes](https://docs.python.org/3/library/ctypes.html#module-ctypes) module.

## Source code

As usual, you can find the related source code on [GitHub](https://github.com/hANSIc99/PythonCppExtension). The C++ files in the repository have the following purpose:

`my_py_module.cpp`

: Definition of the Python module*MyModule*`my_cpp_class.h`

: A header-only C++ class which gets exposed to Python`my_class_py_type.h/cpp`

: Python representation of our C++ class`pydbg.cpp`

: Separate application for debugging purpose

The Python module you build in this article won’t have any meaningful use, but is a good example.

## Build the module

Before looking into the source code, you can check whether the module compiles on your system. [I use CMake](https://opensource.com/article/21/5/cmake) for creating the build configuration, so CMake must be installed on your system. In order to configure and build the module, you can either let Python run the process:

```
````$ python3 setup.py build`


Or run the process manually:

```
``````
$ cmake -B build
$ cmake --build build
```


After that, you have a file called `MyModule.so`

in the `/build`

subdirectory.

## Defining an extension module

First, take a look on `my_py_module.cpp`

, in particular, the function `PyInit_MyModule`

:

```
``````
PyMODINIT_FUNC
PyInit_MyModule(void) {
PyObject* module = PyModule_Create(&my_module);
PyObject *myclass = PyType_FromSpec(&spec_myclass);
if (myclass == NULL){
return NULL;
}
Py_INCREF(myclass);
if(PyModule_AddObject(module, "MyClass", myclass) < 0){
Py_DECREF(myclass);
Py_DECREF(module);
return NULL;
}
return module;
}
```


This is the most important code in this example because it acts as the entry point for CPython. In general, when a Python C extension is compiled and made available as a shared object binary, CPython searches for the function `PyInit_<ModuleName>`

in the eponymous binary (`<ModuleName>.so`

) and executes it when attempting to import it.

All Python types, whether declarations or instances, are exposed as pointers to [PyObject](https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pyobject#c.PyObject). In the first part of this function, the root definition of the module is created by running `PyModule_Create(...)`

. As you can see in the module specification (`my_module`

, same file), it doesn’t have any special functionality.

Afterward, [PyType_FromSpec](https://docs.python.org/3/c-api/type.html#c.PyType_FromSpec) is called to create a Python [heap type](https://docs.python.org/3/c-api/typeobj.html#heap-types) definition for the custom type MyClass. A heap type corresponds to a Python class. The type definition is then assigned to the module MyModule.

*Note that if one of the functions fails, the reference count of previously created PyObjects must be decremented so that they get deleted by the interpreter.*


## Specifying a Python type

The specification for the type MyClass is found inside [my_class_py_type.h](https://github.com/hANSIc99/PythonCppExtension/blob/main/my_class_py_type.h) as an instance of [PyType_Spec](https://docs.python.org/3/c-api/type.html#c.PyType_Spec):

```
``````
static PyType_Spec spec_myclass = {
"MyClass", // name
sizeof(MyClassObject) + sizeof(MyClass), // basicsize
0, // itemsize
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // flags
MyClass_slots // slots
};
```


This structure defines some basic type information for the class. The value passed for the size consists of the size of the Python representation (`MyClassObject`

) and the size of the plain C++ class (`MyClass`

). The `MyClassObject`

is defined as follows:

```
``````
typedef struct {
PyObject_HEAD
int m_value;
MyClass* m_myclass;
} MyClassObject;
```


The Python representation is basically of type [PyObject](https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pyobject#c.PyObject), defined by the macro `PyObject_HEAD`

, and some additional members. The member `m_value`

is exposed as ordinary class member while the member `m_myclass`

is only accessible from inside C++ code.

The [PyType_Slot](https://docs.python.org/release/3.9.1/c-api/type.html?highlight=pytype_slot#c.PyType_Slot) defines some additional functionality:

```
``````
static PyType_Slot MyClass_slots[] = {
{Py_tp_new, (void*)MyClass_new},
{Py_tp_init, (void*)MyClass_init},
{Py_tp_dealloc, (void*)MyClass_Dealloc},
{Py_tp_members, MyClass_members},
{Py_tp_methods, MyClass_methods},
{0, 0} /* Sentinel */
};
```


Here, the jump addressed for some initialization and de-initialization functions are set as well as ordinary class methods and members. Additional functionality, like assigning an initial attribute dictionary, could also be set, but this is optional. Those definitions usually end with a sentinel, consisting of `NULL`

values.

To complete the type specification, here is the method and member table:

```
``````
static PyMethodDef MyClass_methods[] = {
{"addOne", (PyCFunction)MyClass_addOne, METH_NOARGS, PyDoc_STR("Return an incrmented integer")},
{NULL, NULL} /* Sentinel */
};
static struct PyMemberDef MyClass_members[] = {
{"value", T_INT, offsetof(MyClassObject, m_value)},
{NULL} /* Sentinel */
};
```


In the method table, the Python method `addOne`

is defined, and it points to the related function `MyClass_addOne`

. This function acts as a wrapper. It invokes the `addOne()`

method in the C++ class.

In the member table, there is just one member defined for demonstration purposes. Unfortunately, the use of [offsetof](https://en.cppreference.com/w/cpp/types/offsetof) in [PyMemberDef](https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pymemberdef#c.PyMemberDef) doesn’t allow C++ specific types to be added to `MyClassObject`

. If you try to place some C++ type container (such as [std::optional](https://en.cppreference.com/w/cpp/utility/optional)), the compiler complains about it in the form of warnings related to memory layout.

## Initialization and de-initialization

The method `MyClass_new`

acts only to provide initial values for `MyClassObject`

and allocates memory for the base type:

```
``````
PyObject *MyClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds){
std::cout << "MtClass_new() called!" << std::endl;
MyClassObject *self;
self = (MyClassObject*) type->tp_alloc(type, 0);
if(self != NULL){ // -> allocation successfull
// assign initial values
self->m_value = 0;
self->m_myclass = NULL;
}
return (PyObject*) self;
}
```


Actual initialization takes place in `MyClass_init`

, which corresponds to the [__init__()](https://docs.python.org/3/library/dataclasses.html?highlight=__init__) method in Python:

```
``````
int MyClass_init(PyObject *self, PyObject *args, PyObject *kwds){
((MyClassObject *)self)->m_value = 123;
MyClassObject* m = (MyClassObject*)self;
m->m_myclass = (MyClass*)PyObject_Malloc(sizeof(MyClass));
if(!m->m_myclass){
PyErr_SetString(PyExc_RuntimeError, "Memory allocation failed");
return -1;
}
try {
new (m->m_myclass) MyClass();
} catch (const std::exception& ex) {
PyObject_Free(m->m_myclass);
m->m_myclass = NULL;
m->m_value = 0;
PyErr_SetString(PyExc_RuntimeError, ex.what());
return -1;
} catch(...) {
PyObject_Free(m->m_myclass);
m->m_myclass = NULL;
m->m_value = 0;
PyErr_SetString(PyExc_RuntimeError, "Initialization failed");
return -1;
}
return 0;
}
```


If you want to have arguments passed during initialization, you must call [PyArg_ParseTuple](https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple) at this point. For the sake of simplicity, all arguments passed during initialization are ignored in this example. In the first part of the function, the `PyObject`

pointer (`self`

) is reinterpreted to a pointer to `MyClassObject`

in order to get access to our additional members. Additionally, the memory for the C++ class is allocated and its constructor is executed.

Note that exception handling and memory allocation (and de-allocation) must be carefully done in order to prevent memory leaks. When the reference count drops to zero, the `MyClass_dealloc`

function takes care of freeing all related heap memory. There’s a [dedicated chapter](https://docs.python.org/3/c-api/memory.html) in the documentation about memory management for C and C++ extensions.

## Method wrapper

Calling a related C++ class method from the Python class is easy:

```
``````
PyObject* MyClass_addOne(PyObject *self, PyObject *args){
assert(self);
MyClassObject* _self = reinterpret_cast<MyClassObject*>(self);
unsigned long val = _self->m_myclass->addOne();
return PyLong_FromUnsignedLong(val);
}
```


Again, the `PyObject*`

argument (`self`

) is casted to `MyClassObject*`

in order to get access to `m_myclass`

, a pointer to the C++ class instance. With this information, the classes method `addOne()`

is called and the result is returned in form of a [Python integer object](https://docs.python.org/3/c-api/long.html).

## 3 ways to debug

For debugging purposes, it can be valuable to compile the CPython interpreter in debugging configuration. A detailed description can be found in the [official documentation](https://docs.python.org/3/c-api/intro.html#debugging-builds). It’s possible to follow the next steps, as long as additional debug symbols for the pre-installed interpreter are downloaded.

### GNU Debugger

Good old [GNU Debugger (GDB)](https://opensource.com/article/21/3/debug-code-gdb) is, of course, also useful here. I include a [gdbinit](https://github.com/hANSIc99/PythonCppExtension/blob/main/gdbinit) file, defining some options and breakpoints. There’s also the [gdb.sh](https://github.com/hANSIc99/PythonCppExtension/blob/main/gdb.sh) script, which creates a debug build and initiates a GDB session:

![Gnu Debugger (GDB) is useful for your Python C and C++ extensions.](https://opensource.com/sites/default/files/2022-11/gdb_session_b_0.png)

(Stephan Avenwedde, CC BY-SA 4.0)

GDB invokes the CPython interpreter with the script file [main.py](https://github.com/hANSIc99/PythonCppExtension/blob/main/main.py). The script file allows you to easily define all the actions you want to perform with the Python extension module.

### C++ application

Another approach is to embed the CPython interpreter in a separate C++ application. In the repository, this can be found in the file [pydbg.cpp](https://github.com/hANSIc99/PythonCppExtension/blob/main/pydbg.cpp):

```
``````
int main(int argc, char *argv[], char *envp[])
{
Py_SetProgramName(L"DbgPythonCppExtension");
Py_Initialize();
PyObject *pmodule = PyImport_ImportModule("MyModule");
if (!pmodule) {
PyErr_Print();
std::cerr << "Failed to import module MyModule" << std::endl;
return -1;
}
PyObject *myClassType = PyObject_GetAttrString(pmodule, "MyClass");
if (!myClassType) {
std::cerr << "Unable to get type MyClass from MyModule" << std::endl;
return -1;
}
PyObject *myClassInstance = PyObject_CallObject(myClassType, NULL);
if (!myClassInstance) {
std::cerr << "Instantioation of MyClass failed" << std::endl;
return -1;
}
Py_DecRef(myClassInstance); // invoke deallocation
return 0;
}
```


Using the [high level interface](https://docs.python.org/3/extending/embedding.html#very-high-level-embedding), it’s possible to include the extension module and perform actions on it. This allows you to debug in the native IDE environment. It also gives you finer control of the variables passed from and to the extension module.

The drawback is the high expense of creating an extra application.

### VSCode and VSCodium LLDB extension

Using a debugger extension like [CodeLLDB](https://github.com/vadimcn/vscode-lldb) is probably the most convenient debugging option. The repository includes the VSCode or VSCodium configuration files for building the extension ([task.json](https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/tasks.json), [CMake Tools](https://github.com/microsoft/vscode-cmake-tools)) and invoking the debugger ([launch.json](https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/launch.json)). This approach combines the advantages of the previous ones: Debugging in an graphical IDE, defining actions in a Python script file or even dynamically in the interpreter prompt.

![VSCodium features an integrated debugger.](https://opensource.com/sites/default/files/2022-11/vscodium_debug_session.png)

(Stephan Avenwedde, CC BY-SA 4.0)

## Extend C++ with Python

All functionality available from Python code is also available from within a C or C++ extension. While coding in Python is often considered as an easy win, extending Python in C or C++ can also be a pain. On the other hand, while native Python code is slower than C++, a C or C++ extension makes it possible to elevate a computation-intensive task to the speed of native machine code.

You must also consider the usage of an ABI. The stable ABI provides a way to maintain backwards compatibility to older versions of CPython as described in [the documentation](https://docs.python.org/3/c-api/stable.html).

In the end, you must weigh the advantages and disadvantages yourself. Should you decide to use C extensions to make certain functionality available to you in Python, you’ve seen how it can be done.

## Comments are closed.