4. 首次尝试(First steps)
本章将演示pybind11的基本特性。在开始前,请确保正确配置了编译pybind11测试用例的开发环境。
4.1 编译测试用例
Linux/macOS
在Linux上,你需要安装python-dev或python3-dev包和cmake。在macOS上,系统自带了所需的python版本,还需要安装cmake。
在安装好依赖项之后,运行下面的脚本:
mkdir build
cd build
cmake ..
make check -j 4
脚本的最后一行将编译并运行测试用例。
Windows
在Windows上,需要支持C++11的Visual Studio版本(15及其以上)。
Note:在Visual Studio 2017(MSVC 14.1)上使用C++17时,pybind11需要添加标识
/permissive-
来让编译器强制标准一致。在Visual Studio 2019上,不做强制要求,但同样建议添加。
使用以下命令编译和运行测试用例:
mkdir build
cd build
cmake ..
cmake --build . --config Release --target check
命令将在命令行创建Visual Studio工程,编译并运行项目。
Note:如果测试失败了,请确保Python程序和测试用例是由同一类型处理器(如i386或x86_64)编译的。你可以指定x86_64为目标架构来生成vs工程,命令像这样
cmake -A x64 ..
。
4.2 头文件和命名空间约定
为简洁起见,所有代码示例都假定存在以下两行:
#include <pybind11/pybind11.h>
namespace py = pybind11;
某些功能可能需要其他头文件,但会根据需要指定。
4.3 为简单函数创建绑定
我们将从绑定一个简单的加法函数来演示pybind11的使用。
int add(int i, int j) {
return i + j;
}
简单起见,我们将加法函数和绑定代码都放到example.cpp
文件中,内容如下:
#include <pybind11/pybind11.h>
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("add", &add, "A function which adds two numbers");
}
PYBIND11_MODULE
会创建一个函数,它在Python中使用import
语句时被调用。宏的第一个参数是模块名(example),不使用引号包住;第二个参数是类型为py::module_
的变量(m),它是创建绑定的主要接口。module_::def()
方法,则会生成add函数的Python绑定代码。
Note:我们只需要少量的代码就可以将函数暴露给Python,函数入参和返回值相关的细节都由模板元编程自动推断。这种方式和语法是借用Boost.Python的,尽管底层实现完全不同。
pybind11是一个head-only库,它不需要链接任何库,也没有魔法般的中间转换步骤。在Linux上,示例可以使用下面的命令进行编译:
c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
Note:如果你使用子模块的方式包含pybind11代码,这里需要使用
$(python3-config --includes) -Iextern/pybind11/include
代替$(python3 -m pybind11 --includes)
。原因在后续章节会解释。
如果需要更多有关于Linux和MacOS上所需编译标志的详细信息,请参阅手动构建章节。有关完整的跨平台编译说明,请参阅构建系统章节。
编译上面的C++代码后,我们会得到一个二进制模块文件,直接使用import
导入模块到Python中。假设编译好的模块位于当前目录下,Python交互示例代码如下:
>>> import example
>>> example.add(1, 2)
3L
>>>
4.4 关键字参数
这里,我们对上面的C++代码做一点改造,就可以通知Python关于参数的名称(如本例中的“i”和“j”)。
m.def("add", &add, "A function which adds two numbers",
py::arg("i"), py::arg("j"));
arg是可用于将元数据传递到module::def()的几个特殊标记类之一。使用上面修改后的代码,我们可以在调用函数时使用关键字参数,以增加代码可读性,特别是对那些带有多个参数的函数。
import example
example.add(i=1, j=2) #3L
关键字名称也会在文档的函数签名中显示:
>>> help(example)
....
FUNCTIONS
add(...)
Signature : (i: int, j: int) -> int
A function which adds two numbers
还可以使用更加简短的方式给参数命名:
// regular notation
m.def("add1", &add, py::arg("i"), py::arg("j"));
// shorthand
using namespace pybind11::literals;
m.def("add2", &add, "i"_a, "j"_a);
后缀_a
会生成一个等价于arg
方法的字面量。使用这个后缀时,需要调用using namespace pybind11::literals
来声明后缀所在的命名空间。这样除了literals
外,不会从pybind11命名空间引入其他不必要的东西。
4.5 默认参数
现在我们需要绑定一个带默认参数的函数:
int add(int i = 1, int j = 2) {
return i + j;
}
pybind11不能自动地提取默认参数,因为它不属于函数类型信息的一部分。我们需要借助arg
来实现这一功能:
m.def("add", &add, "A function which adds two numbers",
py::arg("i") = 1, py::arg("j") = 2);
默认值同样也会在文档中展示:
>>> help(example)
....
FUNCTIONS
add(...)
Signature : (i: int = 1, j: int = 2) -> int
A function which adds two numbers
更简短的声明方式:
// regular notation
m.def("add1", &add, py::arg("i") = 1, py::arg("j") = 2);
// shorthand
m.def("add2", &add, "i"_a=1, "j"_a=2);
4.6 导出变量
我们可以使用attr
函数来注册需要导出到Python模块中的C++变量。内建类型和常规对象(后面会细讲)会在指定attriutes时自动转换,也可以使用py::cast
来显式转换。
PYBIND11_MODULE(example, m) {
m.attr("the_answer") = 42;
py::object world = py::cast("World");
m.attr("what") = world;
}
``
Python中使用如下:
```pyhton
>>> import example
>>> example.the_answer
42
>>> example.what
'World'
4.7 支持的数据类型
原生支持大量数据类型,完美适用于函数参数,参数值通常直接返回或者经过py::cast处理再返回。有关完整概述,请参阅类型转换部分。(A large number of data types are supported out of the box and can be used seamlessly as functions arguments, return values or with py::cast in general. For a full overview, see the Type conversions section.)