第4章 模块与函数导出
pybind11 的核心任务是在 C++ 和 Python 之间建立桥梁,而模块级函数是这种桥梁中最基本的元素。本章将详细介绍如何定义模块级函数、处理函数重载、配置参数行为,以及添加文档和类型信息。
4.1 模块级函数定义
Section titled “4.1 模块级函数定义”pybind11 使用 PYBIND11_MODULE 宏来创建模块入口点,这是所有 pybind11 模块的标准写法。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
// PYBIND11_MODULE 是 pybind11 创建模块的标准宏// 第一个参数是模块名,第二个参数是模块对象的引用PYBIND11_MODULE(example_module, m) { // 模块级 docstring m.doc() = "example_module - 演示模块级函数导出";
// 使用 m.def() 定义函数 // 语法: m.def("python_name", &cxx_function, "docstring"); m.def("add", [](int a, int b) { return a + b; }, "两个整数相加"); m.def("greet", [](const std::string& name) { return "Hello, " + name + "!"; }, "返回问候语");}Python 使用
Section titled “Python 使用”import example_module as em
result = em.add(1, 2) # 返回 3print(em.greet("World")) # 输出: Hello, World!PYBIND11_MODULE(example_module, m) 的工作原理:
- 模块名 (
example_module): 这是 Python 中import example_module使用的名字 - 模块对象 (
m): 这是一个py::module_类型的引用,在宏内使用它来定义函数、类等 - 宏展开: pybind11 会生成适当的初始化代码,使模块能被 Python 导入
关键概念:
m.def("name", callable, "doc")的第三个参数是函数的 docstring,可以是多行字符串。pybind11 会将这些信息传递给 Python,使help(funcname)能正常工作。
4.2 函数重载
Section titled “4.2 函数重载”当多个 C++ 函数具有相同的名称但不同的参数类型时,pybind11 可以通过函数重载来提供统一的 Python 接口。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_MODULE(overloads, m) { m.doc() = "函数重载演示";
// 方法1: 使用 lambda 直接定义多个重载 m.def("process", py::overload_cast<int>::dispatch([](int x) { return x * 2; }), "处理整数:加倍");
m.def("process", py::overload_cast<double>::dispatch([](double x) { return x * 2.0; }), "处理浮点数:加倍");
m.def("process", py::overload_cast<std::string>::dispatch([](const std::string& s) { return s + s; }), "处理字符串:重复一次");
// 方法2: 使用哑函数 + cast // 定义重载的另一种方式 m.def("compute", static_cast<int(*)(int, int)>(&compute_impl), "计算两个整数的和");
m.def("compute", static_cast<double(*)(double, double)>(&compute_impl), "计算两个浮点数的和");}
// 辅助函数(哑函数)int compute_impl(int a, int b) { return a + b; }double compute_impl(double a, double b) { return a + b; }Python 使用
Section titled “Python 使用”import overloads as ov
print(ov.process(5)) # 调用 int 版本,输出 10print(ov.process(3.14)) # 调用 double 版本,输出 6.28print(ov.process("Hi")) # 调用 string 版本,输出 "HiHi"对于简单的重载,最常用的方式是利用 C++ 的类型推导:
PYBIND11_MODULE(overloads2, m) { // 直接用 lambda 定义重载 m.def("compute", [](int a, int b) { return a + b; }); m.def("compute", [](double a, double b) { return a + b; }); m.def("compute", [](const std::string& a, const std::string& b) { return a + b; });}关键概念:
py::overload_cast<Args>::dispatch()是 pybind11 提供的类型安全重载分派机制。它消除了手动编写哑函数的需要,让代码更清晰。
4.3 默认参数
Section titled “4.3 默认参数”C++ 的默认参数可以直接映射到 Python 的可选参数。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_MODULE(defaults, m) { m.doc() = "默认参数演示";
// 定义带默认参数的函数 // C++: void set_range(int min = 0, int max = 100) // Python: set_range(min=0, max=100) 或 set_range(5) 或 set_range(5, 50) m.def("set_range", [](int min, int max) { // 实际逻辑 return py::make_tuple(min, max); }, py::arg("min") = 0, py::arg("max") = 100, "设置最小值和最大值,默认 min=0, max=100");
// 只有一个默认参数 m.def("set_name", [](const std::string& name, bool uppercase) { return uppercase ? py::str("{:>10}").format(name).cast<std::string>() : name; }, py::arg("name"), py::arg("uppercase") = false, "设置名称,可选是否转换为大写");}Python 使用
Section titled “Python 使用”import defaults as d
print(d.set_range()) # (0, 100)print(d.set_range(10)) # (10, 100)print(d.set_range(10, 50)) # (10, 50)
print(d.set_range(min=20)) # (20, 100)print(d.set_range(max=200)) # (0, 200)
print(d.set_name("john")) # "john"print(d.set_name("john", True)) # " JOHN"m.def("func_name", callable, py::arg("param_name") = default_value, py::arg("other_param") = other_default, "docstring");py::arg("name")指定参数名,这使得关键字参数在 Python 中可用= default_value指定默认值- 参数顺序必须与 C++ 函数签名一致
重要: C++ 默认参数必须与 pybind11 的
py::arg声明顺序一致。如果 C++ 函数有默认参数,pybind11 会根据参数位置自动匹配。
4.4 可选参数(None 处理)
Section titled “4.4 可选参数(None 处理)”Python 的 None 值在 C++ 中需要特殊处理。pybind11 使用 py::none 和 std::optional 来处理这类情况。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>#include <optional>
namespace py = pybind11;
PYBIND11_MODULE(optional_args, m) { m.doc() = "可选参数处理演示";
// 方法1: 使用 std::optional 处理 None m.def("find_user", [](std::optional<int> user_id, std::optional<std::string> username) { if (user_id.has_value()) { return py::str("Found by ID: {}").format(user_id.value()); } else if (username.has_value()) { return py::str("Found by name: {}").format(username.value()); } return py::str("No criteria provided"); }, py::arg("user_id") = std::nullopt, py::arg("username") = std::nullopt, "根据 ID 或用户名查找用户,两者都为 None 时返回提示");
// 方法2: 使用 py::none 处理可选参数 m.def("process_data", [](const std::string& data, py::none callback) { if (callback.is_none()) { return "Data: " + data + " (no callback)"; } return "Data: " + data + " (callback provided)"; }, py::arg("data"), py::arg("callback") = py::none(), "处理数据,callback 为 None 时跳过回调");}Python 使用
Section titled “Python 使用”import optional_args as oa
print(oa.find_user(None, "Alice")) # "Found by name: Alice"print(oa.find_user(42, None)) # "Found by ID: 42"print(oa.find_user(None, None)) # "No criteria provided"
print(oa.find_user(user_id=42)) # "Found by ID: 42"print(oa.find_user(username="Bob")) # "Found by name: Bob"
print(oa.process_data("test")) # "Data: test (no callback)"print(oa.process_data("test", None)) # "Data: test (no callback)"// 当 Python 传递 None 时,C++ 端接收到的实际值py::none x; // py::none 类型对象x.is_none(); // truestd::optional<T> y; // 可选类型,None 时 has_value() == false| C++ 类型 | Python 传 None 时的行为 |
|---|---|
py::none | 接收 py::none 对象,需调用 .is_none() 检查 |
std::optional<T> | 接收 std::nullopt,has_value() 返回 false |
T* (指针) | 接收 nullptr |
4.5 关键字参数
Section titled “4.5 关键字参数”使用 py::kwarg 可以让函数接受任意关键字参数,这在实现配置函数时非常有用。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_MODULE(kwargs, m) { m.doc() = "关键字参数演示";
// 定义一个接受 kwargs 的函数 m.def("configure", [](py::kwargs kwargs) { py::dict result;
// 遍历所有传入的关键字参数 for (auto [key, value] : kwargs) { std::string key_str = py::str(key).cast<std::string>(); py::str val_str = py::str(value); result[key_str] = val_str; }
return result; }, "接受任意关键字参数并返回字典");}Python 使用
Section titled “Python 使用”import kwargs as kw
config = kw.configure(host="localhost", port=8080, debug=True)print(config) # {'host': 'localhost', 'port': 8080, 'debug': True}
empty = kw.configure()print(empty) # {}py::kwargs 特别适合:
- 配置函数:接受多个可选配置项
- 转发函数:将 kwargs 传递给其他函数
- 插件系统:允许动态扩展参数
注意:
py::kwargs参数必须是函数的最后一个参数。
4.6 位置参数限制
Section titled “4.6 位置参数限制”pybind11 允许限制函数参数只能通过位置或关键字传递。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_MODULE(arg_restrictions, m) { m.doc() = "位置参数限制演示";
// 位置参数模式(默认) // Python 调用: func(a, b, c) - 三个都是位置参数 m.def("positional_only", [](int a, int b, int c) { return a + b + c; }, py::arg("a"), py::arg("b"), py::arg("c"), "所有参数只能是位置参数(默认行为)");
// 使用 py::kw_only() 标记关键字参数分隔符 // 在分隔符之前的参数可以使用位置或关键字传递 // 在分隔符之后的参数只能使用关键字传递 m.def("keyword_only", [](int a, int b, int c) { return a + b + c; }, py::arg("a"), // 可以用位置或关键字 py::arg("b"), // 可以用位置或关键字 py::kw_only(), // 分隔符:之后都是关键字-only py::arg("c"), // 只能使用关键字传递 "a 和 b 可以用位置或关键字,c 只能关键字");}Python 使用
Section titled “Python 使用”import arg_restrictions as ar
print(ar.positional_only(1, 2, 3)) # 6print(ar.positional_only(1, 2, c=3)) # 6print(ar.positional_only(a=1, b=2, c=3)) # 6
print(ar.keyword_only(1, 2, c=3)) # 6print(ar.keyword_only(1, b=2, c=3)) # 6| 模式 | 何时使用 |
|---|---|
| 默认(无限制) | 普通函数,任一参数都可用关键字 |
py::kw_only() | 强制某些参数必须使用关键字(如配置参数) |
Python 3.8+ 的 / 标记 | 在 Python 端用 def f(a, /, b) 表示位置参数 |
4.7 文档字符串
Section titled “4.7 文档字符串”好的文档对可维护性至关重要。pybind11 支持为模块、函数和参数提供文档字符串。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_MODULE(docstrings, m) { // 模块级 docstring m.doc() = R"pbdoc( docstrings - 演示文档字符串 ==============================
这个模块展示了 pybind11 文档字符串的各种用法。
示例: import docstrings help(docstrings.add) # 查看函数帮助 )pbdoc";
// 函数 docstring m.def("add", [](int a, int b) { return a + b; }, R"pbdoc( 将两个数字相加
Args: a (int): 第一个数字 b (int): 第二个数字
Returns: int: 两个数字的和
Raises: 无
Example: >>> add(1, 2) 3 )pbdoc", py::arg("a"), py::arg("b"));
// 类 docstring(下一章会讲) // py::class_<MyClass>(m, "MyClass") // .def(py::init<>()) // .doc() = "MyClass 的文档...";}Python 使用
Section titled “Python 使用”import docstrings as ds
print(ds.add.__doc__)
help(ds.add)文档字符串最佳实践
Section titled “文档字符串最佳实践”// 推荐:使用原始字符串常量(R"pbdoc(...)pbdoc")// 这样可以包含换行和引号而不需要转义
m.def("complex_func", [](int x, const std::string& s, double d) { /* ... */ }, R"pbdoc( 复杂函数的文档
这个函数执行以下操作: 1. 验证输入 2. 处理数据 3. 返回结果
Args: x: 整数值,范围 [0, 100] s: 字符串参数,不能为空 d: 浮点数,默认 1.0
Returns: 处理后的结果字符串
Note: 这个函数线程安全 )pbdoc", py::arg("x"), py::arg("s"), py::arg("d") = 1.0);4.8 函数签名
Section titled “4.8 函数签名”Python 的函数签名 introspection 允许 IDE 和工具提供自动补全和参数提示。
C++ 代码
Section titled “C++ 代码”#include <pybind11/pybind11.h>#include <pybind11/stl.h>
namespace py = pybind11;
PYBIND11_MODULE(signature, m) { m.doc() = "函数签名演示";
// 方式1: 隐式签名(默认) m.def("implicit_sig", [](int a, double b, const std::string& c) { return 0; }, "隐式签名:参数名自动从 py::arg 获取");
// 方式2: 显式签名(推荐) m.def("explicit_sig", [](int a, double b, const std::string& c) { return 0; }, R"pbdoc( 显式签名:使用 py::keep_alive 演示复杂签名
Args: a: 第一个参数 b: 第二个参数 c: 第三个参数
Returns: 整数结果 )pbdoc", py::arg("a"), py::arg("b"), py::arg("c"));
// 签名与类型提示(Python 3.8+) m.def("typed_hint", [](int x) -> int { return x * 2; }, py::arg("x"), "带类型提示的函数");
// 多重重载签名 m.def("overloaded_sig", py::overload_cast<int, int>(&sum_impl), py::arg("a"), py::arg("b"), "两个整数求和");
m.def("overloaded_sig", py::overload_cast<double, double>(&sum_impl), py::arg("a"), py::arg("b"), "两个浮点数求和");}
// 辅助函数int sum_impl(int a, int b) { return a + b; }double sum_impl(double a, double b) { return a + b; }Python 使用
Section titled “Python 使用”import signature as sigimport inspect
s = inspect.signature(sig.explicit_sig)print(s) # (a: int, b: float, c: str)
for name, param in s.parameters.items(): print(f" {name}: default={param.default}, annotation={param.annotation}")
print(sig.explicit_sig.__doc__)Signature 注解
Section titled “Signature 注解”pybind11 会自动为函数生成 __annotations__,Python IDE 可以利用这些信息提供类型提示:
@decoratordef wrapper(f): # 通过 inspect.signature(f) 可以获取原始函数的参数类型 pass实用技巧: 配合
py::signature()可以自定义函数签名显示。在大多数情况下,让 pybind11 自动处理即可。
本章介绍了 pybind11 模块和函数导出的核心概念:
| 主题 | 关键点 |
|---|---|
| 模块定义 | PYBIND11_MODULE(name, m) 是标准入口点 |
| 函数定义 | m.def("py_name", &cxx_func, "doc") |
| 函数重载 | py::overload_cast<Args>::dispatch() |
| 默认参数 | py::arg("name") = default_value |
| 可选参数 | std::optional<T> 和 py::none |
| 关键字参数 | py::kwargs 捕获所有关键字参数 |
| 参数限制 | py::kw_only() 标记关键字-only 参数 |
| 文档字符串 | 使用 R"pbdoc(...)pbdoc" 原始字符串 |
| 函数签名 | 自动生成,可通过 inspect.signature() 查看 |
下一章我们将学习如何导出 C++ 类到 Python,包括构造函数、成员函数、继承等高级特性。