构建第一个扩展模块
本章将带你一步步完成一个最简单的 Python C 扩展模块的构建过程,
包括 CMake 配置和绑定一个基础函数,实现 Python 调用 C 函数。
环境准备
- 已安装 Python(建议使用 Python 3.10+)
- 已安装 CMake(对于
VS2022
可直接使用内置的集成版本) - C/C++ 编译器(Windows 下推荐 MSVC,Linux/macOS 推荐 GCC/Clang)
若无特殊声明,文案内容默认以MSVC(VS2022)
+CAMKE
为标准。
编写 CMakeLists.txt
使用 CMake 配置并管理此 C++ 项目。
text
TestLib/
├── CMakeLists.txt
└── main.cpp
cmake
# CMakeLists.txt
cmake_minimum_required (VERSION 3.11)
project(PYTHON_TEST)
if(MSVC)
# 为MSVC设置UTF8编码支持以及C++异常处理支持(可选)
add_compile_options(/utf-8 /EHsc)
endif()
# 包含Python头文件和库目录 Windows可通过CMD: 'where python' 快速查询Python路径
include_directories("D:/Python/Python312/include")
link_directories("D:/Python/Python312/libs")
# 创建一个名为testLib的动态链接库编译目标 其中包含main.cpp源代码文件
add_library(testLib SHARED "main.cpp")
if(WIN32)
# 对于windows平台 需要设置库文件后缀为.pyd而不是.dll(以便python识别)
set_target_properties(testLib PROPERTIES SUFFIX ".pyd")
endif()
# 链接python312.lib库文件(取决于你的python版本)
target_link_libraries(testLib PRIVATE python312.lib)
# 设置C++语言标准为20(可选)
set_property(TARGET testLib PROPERTY CXX_STANDARD 20)
编写模块入口
Python解释器会尝试加载特定符号以便获取模块注册信息。
cpp
// main.cpp
#include <iostream>
#include <Python.h> // 包含Python所需的一系列C-API以及宏定义
// 模块导出函数表
static PyMethodDef moduleMethods[] = {
{NULL, NULL, 0, NULL}, // 末尾标识,用于VM内存搜索的终止符
};
// 入口符号命名规范: PyInit_{lib文件名}
PyMODINIT_FUNC PyInit_testLib()
{
static PyModuleDef moduleDef = {
PyModuleDef_HEAD_INIT,
"testLib", // 定义module的__name__信息
"", // 定义module的__doc__信息
-1, // 不支持多vm模式(即全局模块共享)
moduleMethods, // 模块导出函数表定义
NULL, NULL, NULL, NULL // 其余不常用参数 可忽略/填充默认值NULL
};
// PyModule_Create 创建Module并返回给PyVM
return PyModule_Create(&moduleDef);
}
第一个C函数绑定
实现一个简单的函数绑定。
cpp
// 对于函数而言,PyObject* self代表当前模块对象
static PyObject* pyAdd(PyObject* self, PyObject* args)
{
long long a, b;
// 解析Python函数传参(所有的参数会打包成元组) 并把结果写入特定内存中(&a, &b)
if (!PyArg_ParseTuple(args, "ll", &a, &b))
{
// 参数解析失败,如不符合ll类型(即两个long long)返回NULL指针代表异常信号
return NULL;
}
long long result = a + b;
// 完成计算 构造Py Long对象(int)
return PyLong_FromLong(result);
}
// 添加绑定函数信息
static PyMethodDef moduleMethods[] = {
// {导出函数名, 绑定函数指针, 调用约定, __doc__}
// METH_VARARGS 调用约定, 表示函数接受参数元组 (args),对于无参函数有其他声明
// 常用约定:METH_NOARGS,METH_VARARGS,METH_KEYWORDS(可通过位运算|同时使用多个约定)
{"add", pyAdd, METH_VARARGS, "函数 __doc__ 可忽略"},
{NULL, NULL, 0, NULL},
};
以上代码编译后将生成的.pyd文件放置在python项目内/配置到解释器环境库中即可import使用。
- 代码测试
python
import testLib # 确保可以搜索到目标pyd文件
print(testLib.add(10000, 2000)) # -> 正常输出计算结果
编写pyi文件
扩展模块为二进制分发,编辑器/IDE无法正常完成分析。
python
# testLib.pyi (放置在testLib.pyd同级)
# 通过pyi文件提供声明信息,可辅助开发工具完成分析高亮补全。
def add(a: int, b: int) -> int: ...
CPP-STD类型交互
局部混用C++类型以提升维护性与开发效率。
cpp
// 检查字符串开头是否包含另外一个字符串的内容
static PyObject* pyStartsWith(PyObject* self, PyObject* args)
{
const char* str;
const char* testHeadStr;
if (!PyArg_ParseTuple(args, "ss", &str, &testHeadStr))
{
return NULL;
}
// string_view类型需要CPP17+版本 其中starts_with则是20+
std::string_view cppStrRef {str};
// 由于C语言并没有bool概念 需要用int来构造PyBool
return PyBool_FromLong(cppStrRef.starts_with(testHeadStr) ? 1 : 0);
}
static PyMethodDef moduleMethods[] = {
{"add", pyAdd, METH_VARARGS, ""},
{"startsWith", pyStartsWith, METH_VARARGS, ""},
{NULL, NULL, 0, NULL},
};
- 代码测试
python
import testLib
print(testLib.add(10000, 2000))
print(testLib.startsWith("abc", "ab"))
总结
本章介绍了如何构建一个基础的 Python C 扩展模块,涵盖了从环境准备、CMake 配置、模块入口定义到函数绑定的完整流程。
在实际开发中,建议结合项目需求灵活运用,并注意 Python 与 C/C++ 之间的数据类型和内存管理差异,确保代码稳定高效。