目次
ソース コード
モジュールの構築
拡張モジュールの定義
指定 Python 类型
初始化和析构
包装方法
3 种方法调试
GNU 调试器
C++ 应用
VSCode 和 VSCodium LLDB 扩展
用 C++ 扩展 Python
ホームページ バックエンド開発 Python チュートリアル Python 用の C++ 拡張モジュールを作成する

Python 用の C++ 拡張モジュールを作成する

Apr 12, 2023 pm 01:34 PM
python モジュール c++

Python 用の C++ 拡張モジュールを作成する

C 拡張機能を使用して、Python に特定の機能を提供します。

前回の記事では、 6 つの Python インタプリタ を紹介しました。 CPython はほとんどのシステムのデフォルトのインタープリタであり、世論調査によると最も人気があります。 Cpython に特有の機能は、拡張 API を使用して C で Python モジュールを作成できることです。 Python モジュールを C で作成すると、Python の使いやすさを維持したまま、計算負荷の高いコードを C に移行できます。

この記事では、C 拡張モジュールの作成方法を説明します。ほとんどのコンパイラは通常両方の言語を理解できるため、C の代わりに C を使用してください。欠点を前もって述べておかなければなりません。この方法で構築された Python モジュールは、他のインタープリタに移植できません。これらは CPython インタープリターでのみ動作します。したがって、C 言語モジュールを操作するためのより移植性の高い方法を探している場合は、 ctypes モジュールの使用を検討してください。

ソース コード

いつものように、関連するソース コードは GitHub で見つけることができます。ウェアハウス内の C ファイルには次の目的があります:

  • my_py_module.cpp : Python モジュールの定義 MyModule
  • my_cpp_class.h : 1 つのヘッダー ファイル - Python に公開される単なる C クラス
  • my_class_py_type.h/cpp : Python の C クラス
  • pydbg.cpp : デバッグ用の別のアプリケーション

この記事で構築された Python モジュールには Of がありません実際には何の役にも立ちませんが、これは良い例です。

モジュールの構築

ソース コードを表示する前に、システム上でコンパイルできるかどうかを確認できます。 CMake を使用してビルド構成情報を作成するため、CMake がシステムにインストールされている必要があります。このモジュールを構成してビルドするには、Python にこのプロセスを実行させることができます:

$ python3 setup.py build
ログイン後にコピー

または手動で:

$ cmake -B build$ cmake --build build
ログイン後にコピー

その後、 /build 次に、 MyModule.so という名前のファイルが作成されます。

拡張モジュールの定義

まず、#my_py_module.cpp# ファイル、特に #PyInit_MyModule# を見てください。 function:

PyMODINIT_FUNCPyInit_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​​ 的一个指针。在此函数的第一部分中,​​module​​ 通过 ​​PyModule_Create(...)​​ 创建的。正如你在 ​​module​​ 详述(​​my_py_module​​,同名文件)中看到的,它没有任何特殊的功能。

之后,调用 ​​PyType_FromSpec​​ 为自定义类型 ​​MyClass​​ 创建一个 Python ​​堆类型​​ 定义。一个堆类型对应于一个 Python 类,然后将它赋值给 ​​MyModule​​ 模块。

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

指定 Python 类型

​MyClass​​ 详述在 ​​my_class_py_type.h​​ 中可以找到,它作为 ​​PyType_Spec​​ 的一个实例:

static PyType_Spec spec_myclass = {"MyClass",// namesizeof(MyClassObject) + sizeof(MyClass),// basicsize0,// itemsizePy_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, // flagsMyClass_slots // slots};
ログイン後にコピー

它定义了一些基本类型信息,它的大小包括 Python 表示的大小(​​MyClassObject​​)和普通 C++ 类的大小(​​MyClass​​)。​​MyClassObject​​ 定义如下:

typedef struct {PyObject_HEADint m_value;MyClass*m_myclass;} MyClassObject;
ログイン後にコピー

Python 表示的话就是 ​​PyObject​​ 类型,由 ​​PyObject_HEAD​​ 宏和其他一些成员定义。成员 ​​m_value​​ 视为普通类成员,而成员 ​​m_myclass​​ 只能在 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​​ 中使用的 ​​offsetof​​ 不允许添加 C++ 类型到 ​​MyClassObject​​。如果你试图放置一些 C++ 类型的容器(如 ​​std::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__()​​ 方法:

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​​。简单起见,本例将忽略初始化过程中传递的所有参数。在函数的第一部分中,​​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 整数对象​​ 返回。

3 种方法调试

出于调试目的,在调试配置中编译 CPython 解释器是很有价值的。详细描述参阅 ​​官方文档​​。只要下载了预安装的解释器的其他调试符号,就可以按照下面的步骤进行操作。

GNU 调试器

当然,老式的 ​​GNU 调试器(GDB)​​ 也可以派上用场。源码中包含了一个 ​​gdbinit​​ 文件,定义了一些选项和断点,另外还有一个 ​​gdb.sh​​ 脚本,它会创建一个调试构建并启动一个 GDB 会话:

Gnu 调试器(GDB)对于 Python C 和 C++ 扩展非常有用

Gnu 调试器(GDB)对于 Python C 和 C++ 扩展非常有用

GDB 使用脚本文件 ​​main.py​​ 调用 CPython 解释器,它允许你轻松定义你想要使用 Python 扩展模块执行的所有操作。

C++ 应用

另一种方法是将 CPython 解释器嵌入到一个单独的 C++ 应用程序中。可以在仓库的 ​​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 deallocationreturn 0;}
ログイン後にコピー

使用 ​​高级接口​​,可以导入扩展模块并对其执行操作。它允许你在本地 IDE 环境中进行调试,还能让你更好地控制传递或来自扩展模块的变量。

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

VSCode 和 VSCodium LLDB 扩展

使用像 ​​CodeLLDB​​ 这样的调试器扩展可能是最方便的调试选项。仓库包含了一些 VSCode/VSCodium 的配置文件,用于构建扩展,如 ​​task.json​​、​​CMake Tools​​ 和调用调试器(​​launch.json​​)。这种方法结合了前面几种方法的优点:在图形 IDE 中调试,在 Python 脚本文件中定义操作,甚至在解释器提示符中动态定义操作。

VSCodium 有一个集成的调试器。

VSCodium 有一个集成的调试器。

用 C++ 扩展 Python

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

你还必须考虑 ABI 的使用。稳定的 ABI 提供了一种方法来保持旧版本 CPython 的向后兼容性,如 ​​文档​​ 所述。

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

以上がPython 用の C++ 拡張モジュールを作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

C#対C:歴史、進化、将来の見通し C#対C:歴史、進化、将来の見通し Apr 19, 2025 am 12:07 AM

C#とCの歴史と進化はユニークであり、将来の見通しも異なります。 1.Cは、1983年にBjarnestrostrupによって発明され、オブジェクト指向のプログラミングをC言語に導入しました。その進化プロセスには、C 11の自動キーワードとラムダ式の導入など、複数の標準化が含まれます。C20概念とコルーチンの導入、将来のパフォーマンスとシステムレベルのプログラミングに焦点を当てます。 2.C#は2000年にMicrosoftによってリリースされました。CとJavaの利点を組み合わせて、その進化はシンプルさと生産性に焦点を当てています。たとえば、C#2.0はジェネリックを導入し、C#5.0は非同期プログラミングを導入しました。これは、将来の開発者の生産性とクラウドコンピューティングに焦点を当てます。

Golang and C:Concurrency vs. Raw Speed Golang and C:Concurrency vs. Raw Speed Apr 21, 2025 am 12:16 AM

Golangは並行性がCよりも優れていますが、Cは生の速度ではGolangよりも優れています。 1)Golangは、GoroutineとChannelを通じて効率的な並行性を達成します。これは、多数の同時タスクの処理に適しています。 2)Cコンパイラの最適化と標準ライブラリを介して、極端な最適化を必要とするアプリケーションに適したハードウェアに近い高性能を提供します。

Golang vs. Python:パフォーマンスとスケーラビリティ Golang vs. Python:パフォーマンスとスケーラビリティ Apr 19, 2025 am 12:18 AM

Golangは、パフォーマンスとスケーラビリティの点でPythonよりも優れています。 1)Golangのコンピレーションタイプの特性と効率的な並行性モデルにより、高い並行性シナリオでうまく機能します。 2)Pythonは解釈された言語として、ゆっくりと実行されますが、Cythonなどのツールを介してパフォーマンスを最適化できます。

Python vs. C:曲線と使いやすさの学習 Python vs. C:曲線と使いやすさの学習 Apr 19, 2025 am 12:20 AM

Pythonは学習と使用が簡単ですが、Cはより強力ですが複雑です。 1。Python構文は簡潔で初心者に適しています。動的なタイピングと自動メモリ管理により、使いやすくなりますが、ランタイムエラーを引き起こす可能性があります。 2.Cは、高性能アプリケーションに適した低レベルの制御と高度な機能を提供しますが、学習しきい値が高く、手動メモリとタイプの安全管理が必要です。

Golang vs. C:パフォーマンスと速度の比較 Golang vs. C:パフォーマンスと速度の比較 Apr 21, 2025 am 12:13 AM

Golangは迅速な発展と同時シナリオに適しており、Cは極端なパフォーマンスと低レベルの制御が必要なシナリオに適しています。 1)Golangは、ごみ収集と並行機関のメカニズムを通じてパフォーマンスを向上させ、高配列Webサービス開発に適しています。 2)Cは、手動のメモリ管理とコンパイラの最適化を通じて究極のパフォーマンスを実現し、埋め込みシステム開発に適しています。

CおよびXML:関係とサポートの調査 CおよびXML:関係とサポートの調査 Apr 21, 2025 am 12:02 AM

Cは、サードパーティライブラリ(TinyXML、PUGIXML、XERCES-Cなど)を介してXMLと相互作用します。 1)ライブラリを使用してXMLファイルを解析し、それらをC処理可能なデータ構造に変換します。 2)XMLを生成するときは、Cデータ構造をXML形式に変換します。 3)実際のアプリケーションでは、XMLが構成ファイルとデータ交換に使用されることがよくあり、開発効率を向上させます。

Pythonプロジェクトは階層化する必要がありますか? Pythonプロジェクトは階層化する必要がありますか? Apr 19, 2025 pm 10:06 PM

Pythonプロジェクトの階層構造に関する議論Pythonを学習する過程で、多くの初心者がいくつかのオープンソースプロジェクト、特にDjangoフレームワークを使用したプロジェクトと接触します...

バックエンド開発における階層アーキテクチャでビジネスロジックと非ビジネスロジックを正しく分割する方法は? バックエンド開発における階層アーキテクチャでビジネスロジックと非ビジネスロジックを正しく分割する方法は? Apr 19, 2025 pm 07:15 PM

バックエンド開発における階層アーキテクチャの問題について議論します。バックエンド開発では、一般的な階層アーキテクチャにはコントローラー、サービス、DAOが含まれます。

See all articles