From 2537136afd56ea80e474a9bd89b500f960c34599 Mon Sep 17 00:00:00 2001 From: MjSeven Date: Sun, 1 Jan 2023 21:33:06 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...­ï¸â­ï¸ Write a C++ extension module for Python.md | 318 ----------------- ...­ï¸â­ï¸ Write a C++ extension module for Python.md | 320 ++++++++++++++++++ 2 files changed, 320 insertions(+), 318 deletions(-) delete mode 100644 sources/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md create mode 100644 translated/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md diff --git a/sources/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md b/sources/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md deleted file mode 100644 index f5b8157ba3..0000000000 --- a/sources/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md +++ /dev/null @@ -1,318 +0,0 @@ -[#]: subject: "Write a C++ extension module for Python" -[#]: via: "https://opensource.com/article/22/11/extend-c-python" -[#]: author: "Stephan Avenwedde https://opensource.com/users/hansic99" -[#]: collector: "lkxed" -[#]: translator: "MjSeven" -[#]: reviewer: " " -[#]: publisher: " " -[#]: url: " " - -Write a C++ extension module for Python -====== - -In a previous article, I gave an overview of [six Python interpreters][1]. 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][2] module. - -### Source code - -As usual, you can find the related source code on [GitHub][3]. 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][4] 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_` in the eponymous binary (`.so`) and executes it when attempting to import it. - -All Python types, whether declarations or instances, are exposed as pointers to [PyObject][5]. 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][6] is called to create a Python [heap type][7] 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][8] as an instance of [PyType_Spec][9]: - -``` -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][5], 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][10] 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][11] in [PyMemberDef][12] doesn’t allow C++ specific types to be added to `MyClassObject`. If you try to place some C++ type container (such as [std::optional][13]), 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__()][14] 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][15] 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][16] 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(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][17]. - -### 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][18]. 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)][19] is, of course, also useful here. I include a [gdbinit][20] file, defining some options and breakpoints. There’s also the [gdb.sh][21] script, which creates a debug build and initiates a GDB session: - -![Gnu Debugger (GDB) is useful for your Python C and C++ extensions.][22] - -GDB invokes the CPython interpreter with the script file [main.py][23]. 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][24]: - -``` -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][25], 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][26] is probably the most convenient debugging option. The repository includes the VSCode or VSCodium configuration files for building the extension ([task.json][27], [CMake Tools][28]) and invoking the debugger ([launch.json][29]). 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.][30] - -### 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][31]. - -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. - --------------------------------------------------------------------------------- - -via: https://opensource.com/article/22/11/extend-c-python - -作者:[Stephan Avenwedde][a] -选题:[lkxed][b] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) è£èª‰æŽ¨å‡º - -[a]: https://opensource.com/users/hansic99 -[b]: https://github.com/lkxed -[1]: https://opensource.com/article/22/9/python-interpreters-2022 -[2]: https://docs.python.org/3/library/ctypes.html#module-ctypes -[3]: https://github.com/hANSIc99/PythonCppExtension -[4]: https://opensource.com/article/21/5/cmake -[5]: https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pyobject#c.PyObject -[6]: https://docs.python.org/3/c-api/type.html#c.PyType_FromSpec -[7]: https://docs.python.org/3/c-api/typeobj.html#heap-types -[8]: https://github.com/hANSIc99/PythonCppExtension/blob/main/my_class_py_type.h -[9]: https://docs.python.org/3/c-api/type.html#c.PyType_Spec -[10]: https://docs.python.org/release/3.9.1/c-api/type.html?highlight=pytype_slot#c.PyType_Slot -[11]: https://en.cppreference.com/w/cpp/types/offsetof -[12]: https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pymemberdef#c.PyMemberDef -[13]: https://en.cppreference.com/w/cpp/utility/optional -[14]: https://docs.python.org/3/library/dataclasses.html?highlight=__init__ -[15]: https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple -[16]: https://docs.python.org/3/c-api/memory.html -[17]: https://docs.python.org/3/c-api/long.html -[18]: https://docs.python.org/3/c-api/intro.html#debugging-builds -[19]: https://opensource.com/article/21/3/debug-code-gdb -[20]: https://github.com/hANSIc99/PythonCppExtension/blob/main/gdbinit -[21]: https://github.com/hANSIc99/PythonCppExtension/blob/main/gdb.sh -[22]: https://opensource.com/sites/default/files/2022-11/gdb_session_b_0.png -[23]: https://github.com/hANSIc99/PythonCppExtension/blob/main/main.py -[24]: https://github.com/hANSIc99/PythonCppExtension/blob/main/pydbg.cpp -[25]: https://docs.python.org/3/extending/embedding.html#very-high-level-embedding -[26]: https://github.com/vadimcn/vscode-lldb -[27]: https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/tasks.json -[28]: https://github.com/microsoft/vscode-cmake-tools -[29]: https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/launch.json -[30]: https://opensource.com/sites/default/files/2022-11/vscodium_debug_session.png -[31]: https://docs.python.org/3/c-api/stable.html diff --git a/translated/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md b/translated/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md new file mode 100644 index 0000000000..184640b701 --- /dev/null +++ b/translated/tech/20221124.4 â­ï¸â­ï¸â­ï¸ Write a C++ extension module for Python.md @@ -0,0 +1,320 @@ +[#]: subject: "Write a C++ extension module for Python" +[#]: via: "https://opensource.com/article/22/11/extend-c-python" +[#]: author: "Stephan Avenwedde https://opensource.com/users/hansic99" +[#]: collector: "lkxed" +[#]: translator: "MjSeven" +[#]: reviewer: " " +[#]: publisher: " " +[#]: url: " " + +为 Python 写一个 C++ æ‰©å±•æ¨¡å— +====== + +使用 C 扩展为 Python æ供特定功能。 + +在å‰ä¸€ç¯‡æ–‡ç« ä¸­ï¼Œæˆ‘介ç»äº†[六个 Python 解释器][1]。在大多数系统上,CPython 是默认的解释器,而且根æ®æ°‘æ„调查显示,它还是最æµè¡Œçš„解释器。Cpython 的独有功能是使用扩展 API 用 C 语言编写 Python 模å—。用 C 语言编写 Python 模å—å…许你将计算密集型代ç è½¬ç§»åˆ° C,åŒæ—¶ä¿ç•™ Python 的易用性。 + +在本文中,我将å‘你展示如何编写一个 C++ 扩展模å—。使用 C++ 而ä¸æ˜¯ C,因为大多数编译器通常都能ç†è§£è¿™ä¸¤ç§è¯­è¨€ã€‚我必须æå‰è¯´æ˜Žç¼ºç‚¹ï¼šä»¥è¿™ç§æ–¹å¼æž„建的 Python 模å—ä¸èƒ½ç§»æ¤åˆ°å…¶ä»–解释器中。它们åªä¸Ž CPython 解释器é…åˆå·¥ä½œã€‚因此,如果你正在寻找一ç§å¯ç§»æ¤æ€§æ›´å¥½çš„与 C 语言模å—交互的方å¼ï¼Œè€ƒè™‘下使用 [ctypes][2]模å—。 + +### æºä»£ç  + +和往常一样,你å¯ä»¥åœ¨ [GitHub][3] 上找到相关的æºä»£ç ã€‚仓库中的 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][4]æ¥åˆ›å»ºæž„建的é…置信æ¯ï¼Œå› æ­¤ä½ çš„系统上必须安装 CMake。为了é…置和构建这个模å—,å¯ä»¥è®© Python 去执行这个过程: + +``` +$ python3 setup.py build +``` + +或者手动执行: + +``` +$ cmake -B build +$ cmake --build build +``` + +之åŽï¼Œåœ¨ `/build` å­ç›®å½•ä¸‹ä½ ä¼šæœ‰ä¸€ä¸ªå为 `MyModule. so` 的文件。 + +### å®šä¹‰æ‰©å±•æ¨¡å— + +首先,看一下 `my_py_module.cpp` 文件,尤其是 `PyInit_MyModule` 函数: + +``` cpp +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 会在åŒå二进制文件中(`.so`)æœç´¢ `PyInit_` 函数,并在试图导入时执行它。 + +无论是声明还是实例,所有 Python 类型都是 [PyObject][5] 的一个指针。在此函数的第一部分中,`module` 通过 `PyModule_Create(...)` 创建的。正如你在 `module` 详述(`my_py_module`,åŒå文件)中看到的,它没有任何特殊的功能。 + +之åŽï¼Œè°ƒç”¨ [PyType_FromSpec][6] 为自定义类型 MyClass 创建一个 Python [堆类型][7]定义。一个堆类型对应于一个 Python 类,然åŽå°†å®ƒèµ‹å€¼ç»™ MyModule 模å—。 + +_注æ„,如果其中一个函数返回失败,则必须å‡å°‘以å‰åˆ›å»ºçš„å¤åˆ¶å¯¹è±¡çš„引用计数,以便解释器删除它们。_ + +### 指定 Python 类型 + +MyClass 详设在 [my_class_py_type.h][8] 中å¯ä»¥æ‰¾åˆ°ï¼Œå®ƒä½œä¸º [PyType_Spec][9] 的一个实例: + +```cpp +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` 定义如下: + +```cpp +typedef struct { + PyObject_HEAD + int m_value; + MyClass* m_myclass; +} MyClassObject; +``` + +Python 表示的è¯å°±æ˜¯ [PyObject][5] 类型,由 `PyObject_HEAD` å®å’Œå…¶ä»–一些æˆå‘˜å®šä¹‰ã€‚æˆå‘˜ `m_value` 视为普通类æˆå‘˜ï¼Œè€Œæˆå‘˜ `m_myclass` åªèƒ½åœ¨ C++ 代ç å†…部访问。 + +[PyType_Slot][10] 定义了一些其他功能: + +```cpp +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` 值。 + +è¦å®Œæˆç±»åž‹è¯¦è¿°ï¼Œè¿˜åŒ…括下é¢çš„方法和æˆå‘˜è¡¨ï¼š + +```cpp +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][12] 中使用的 [offsetof][11] ä¸å…许添加 C++ 类型到 `MyClassObject`。如果你试图放置一些 C++ 类型的容器(如 [std::optional][13]),编译器会抱怨一些内存布局相关的警告。 + +### åˆå§‹åŒ–å’Œæžæž„ + +`MyClass_new` 方法åªä¸º `MyClassObject` æ供一些åˆå§‹å€¼ï¼Œå¹¶ä¸ºå…¶ç±»åž‹åˆ†é…内存: + +```cpp +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__()][14] 方法: + +```cpp +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][15]。简å•èµ·è§ï¼Œæœ¬ä¾‹å°†å¿½ç•¥åˆå§‹åŒ–过程中传递的所有å‚数。在函数的第一部分中,`PyObject` 指针(`self`)被强转为 `MyClassObject` 类型的指针,以便访问其他æˆå‘˜ã€‚此外,还分é…了 C++ 类的内存,并执行了构造函数。 + +注æ„,为了防止内存泄æ¼ï¼Œå¿…须仔细执行异常处ç†å’Œå†…存分é…(还有释放)。当引用计数将为零时,`MyClass_dealloc` 函数负责释放所有相关的堆内存。在文档中有一个章节专门讲述关于 C å’Œ C++ 扩展的内存管ç†ã€‚ + +### 包装方法 + +从 Python 类中调用相关的 C++ 类方法很简å•ï¼š + +```cpp +PyObject* MyClass_addOne(PyObject *self, PyObject *args){ + assert(self); + + MyClassObject* _self = reinterpret_cast(self); + unsigned long val = _self->m_myclass->addOne(); + return PyLong_FromUnsignedLong(val); +} +``` + +åŒæ ·ï¼Œ`PyObject` å‚数(`self`)被强转为 `MyClassObject` 类型以便访问 `m_myclass`ï¼Œå®ƒæŒ‡å‘ C++ 对应类实例的指针。有了这些信æ¯ï¼Œè°ƒç”¨ `addOne()` 类方法,并且结果以[ Python 整数对象][17]返回。 + +### 3 ç§æ–¹æ³•è°ƒè¯• + +出于调试目的,在调试é…置中编译 CPython 解释器是很有价值的。详细æè¿°å‚阅[官方文档][18]。åªè¦ä¸‹è½½äº†é¢„安装的解释器的其他调试符å·ï¼Œå°±å¯ä»¥æŒ‰ç…§ä¸‹é¢çš„步骤进行æ“作。 + +#### GNU 调试器 + +当然,è€å¼çš„ [GNU 调试器(GDB)][19]也å¯ä»¥æ´¾ä¸Šç”¨åœºã€‚æºç ä¸­åŒ…å«äº†ä¸€ä¸ª [gdbinit][20] 文件,定义了一些选项和断点,å¦å¤–还有一个 [gdb.sh][21] 脚本,它会创建一个调试构建并å¯åŠ¨ä¸€ä¸ª GDB 会è¯ï¼š + +![Gnu 调试器(GDB)对于 Python C å’Œ C++ 扩展éžå¸¸æœ‰ç”¨][22] + +GDB 使用脚本文件 [main.py][23] 调用 CPython 解释器,它å…许你轻æ¾å®šä¹‰ä½ æƒ³è¦ä½¿ç”¨ Python 扩展模å—执行的所有æ“作。 + +#### C++ 应用 + +å¦ä¸€ç§æ–¹æ³•æ˜¯å°† CPython 解释器嵌入到一个å•ç‹¬çš„ C++ 应用程åºä¸­ã€‚å¯ä»¥åœ¨ä»“库的 [pydbg.cpp][24] 文件中找到: + +```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; +} +``` + +使用[高级接å£][25],å¯ä»¥å¯¼å…¥æ‰©å±•æ¨¡å—并对其执行æ“作。它å…许你在本地 IDE 环境中进行调试,还能让你更好地控制传递或æ¥è‡ªæ‰©å±•æ¨¡å—çš„å˜é‡ã€‚ + +缺点是创建一个é¢å¤–的应用程åºçš„æˆæœ¬å¾ˆé«˜ã€‚ + +#### VSCode å’Œ VSCodium LLDB 扩展 + +ä½¿ç”¨åƒ [CodeLLDB][26] 这样的调试器扩展å¯èƒ½æ˜¯æœ€æ–¹ä¾¿çš„调试选项。仓库包å«äº†ä¸€äº› VSCode/VSCodium çš„é…置文件,用于构建扩展,如 [task.json][27]ã€[CMake Tools][28] 和调用调试器([launch.json][29])。这ç§æ–¹æ³•ç»“åˆäº†å‰é¢å‡ ç§æ–¹æ³•çš„优点:在图形 IDE 中调试,在 Python 脚本文件中定义æ“作,甚至在解释器æ示符中动æ€å®šä¹‰æ“作。 + +![VSCodium 有一个集æˆçš„调试器。][30] + +### 用 C++ 扩展 Python + +Python 的所有功能也å¯ä»¥ä»Ž C 或 C++ 扩展中获得。虽然用 Python 写代ç é€šå¸¸è®¤ä¸ºæ˜¯ä¸€ä»¶å®¹æ˜“的事情,但用 C 或 C++ 扩展 Python 代ç æ˜¯ä¸€ä»¶ç—›è‹¦çš„事情。å¦ä¸€æ–¹é¢ï¼Œè™½ç„¶åŽŸç”Ÿ Python 代ç æ¯” C++ 慢,但 C 或 C++ 扩展å¯ä»¥å°†è®¡ç®—密集型任务æå‡åˆ°åŽŸç”Ÿæœºå™¨ç çš„速度。 + +你还必须考虑 ABI 的使用。稳定的 ABI æ供了一ç§æ–¹æ³•æ¥ä¿æŒæ—§ç‰ˆæœ¬ CPython çš„å‘åŽå…¼å®¹æ€§ï¼Œå¦‚[文档][31]所述。 + +最åŽï¼Œä½ å¿…须自己æƒè¡¡åˆ©å¼Šã€‚如果你决定使用 C 语言æ¥æ‰©å±• Python 中的一些功能,你已ç»çœ‹åˆ°äº†å¦‚何实现它。 + +-------------------------------------------------------------------------------- + +via: https://opensource.com/article/22/11/extend-c-python + +作者:[Stephan Avenwedde][a] +选题:[lkxed][b] +译者:[MjSeven](https://github.com/MjSeven) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) è£èª‰æŽ¨å‡º + +[a]: https://opensource.com/users/hansic99 +[b]: https://github.com/lkxed +[1]: https://opensource.com/article/22/9/python-interpreters-2022 +[2]: https://docs.python.org/3/library/ctypes.html#module-ctypes +[3]: https://github.com/hANSIc99/PythonCppExtension +[4]: https://opensource.com/article/21/5/cmake +[5]: https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pyobject#c.PyObject +[6]: https://docs.python.org/3/c-api/type.html#c.PyType_FromSpec +[7]: https://docs.python.org/3/c-api/typeobj.html#heap-types +[8]: https://github.com/hANSIc99/PythonCppExtension/blob/main/my_class_py_type.h +[9]: https://docs.python.org/3/c-api/type.html#c.PyType_Spec +[10]: https://docs.python.org/release/3.9.1/c-api/type.html?highlight=pytype_slot#c.PyType_Slot +[11]: https://en.cppreference.com/w/cpp/types/offsetof +[12]: https://docs.python.org/release/3.9.1/c-api/structures.html?highlight=pymemberdef#c.PyMemberDef +[13]: https://en.cppreference.com/w/cpp/utility/optional +[14]: https://docs.python.org/3/library/dataclasses.html?highlight=__init__ +[15]: https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple +[16]: https://docs.python.org/3/c-api/memory.html +[17]: https://docs.python.org/3/c-api/long.html +[18]: https://docs.python.org/3/c-api/intro.html#debugging-builds +[19]: https://opensource.com/article/21/3/debug-code-gdb +[20]: https://github.com/hANSIc99/PythonCppExtension/blob/main/gdbinit +[21]: https://github.com/hANSIc99/PythonCppExtension/blob/main/gdb.sh +[22]: https://opensource.com/sites/default/files/2022-11/gdb_session_b_0.png +[23]: https://github.com/hANSIc99/PythonCppExtension/blob/main/main.py +[24]: https://github.com/hANSIc99/PythonCppExtension/blob/main/pydbg.cpp +[25]: https://docs.python.org/3/extending/embedding.html#very-high-level-embedding +[26]: https://github.com/vadimcn/vscode-lldb +[27]: https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/tasks.json +[28]: https://github.com/microsoft/vscode-cmake-tools +[29]: https://github.com/hANSIc99/PythonCppExtension/blob/main/.vscode/launch.json +[30]: https://opensource.com/sites/default/files/2022-11/vscodium_debug_session.png +[31]: https://docs.python.org/3/c-api/stable.html