15. 常见问题
15.1 “ImportError: dynamic module does not define init function”
- 确保
PYBIND11_MODULE
中指定的名称与扩展库的文件名相同(没有后缀,例如.so)。 - 如果上述问题没有解决,您可能使用了不兼容的
Python
版本(例如,扩展库是针对Python2
编译的,而解释器是在Python3
的某些版本上运行的)。
15.2 “Symbol not found: __Py_ZeroStruct _PyInstanceMethod_Type”
参见15.1
15.3 “SystemError: dynamic module not initialized properly”
参见15.1
15.4 导入模块时,Python解释器立即崩溃
参见15.1
15.5涉及引用参数的限制
在C++
中,使用可变引用或可变指针传递参数是很常见的,这两种方法都允许读取并对调用者提供的值进行写访问。这有时是出于效率原因或为了实现具有多个返回值的函数。这里有两个非常基本的例子:
void increment(int &i)
{
i++;
}
void increment_ptr(int *i)
{
(*i)++;
}
在Python
中,所有参数都是通过引用传递的,因此从Python绑定此类代码时一般不会有问题。 但是,某些基本Python
类型(如str
、int
、bool
、float
等)是不可变的。这意味着尝试将函数移植到Python
后,对调用者提供的值不会产生相同的影响,事实上,它啥都没干。
def increment(i):
i += 1 # nope..
pybind11
也受到此类语言级约定的影响,这意味着绑定increment
或者increment_ptr
还将创建不修改其参数的Python
函数。虽然不方便,但一种解决方法是将不可变类型封装在允许修改的自定义类型中影响。另一种选择涉及绑定一个小包装lambda
函数,该函数返回一个包含所有输出参数的元组 (有关绑定lambda
函数的示例,请参阅文档的其余部分)。例如:
int foo(int &i)
{
i++;
return 123;
}
绑定代码为:
m.def("foo",
[](int i) {
int rv = foo(i);
return std::make_tuple(rv, i);
});
15.6 如何减少编译时间?
在多个文件上拆分绑定代码是一种很好的做法,如下例所示:
example.cpp
void init_ex1(py::module_ &);
void init_ex2(py::module_ &);
/* ... */
PYBIND11_MODULE(example, m) {
init_ex1(m);
init_ex2(m);
/* ... */
}
ex1.cpp
:
void init_ex1(py::module_ &m) {
m.def("add",
[](int a, int b) {
return a + b;
});
}
ex2.cpp
:
void init_ex2(py::module_ &m) {
m.def("sub",
[](int a, int b) {
return a - b;
});
}
python
调用:
import example
example.add(1, 2) # 3
example.sub(1, 1) # 0
如上所示,各种init_ex
函数应该包含在单独的文件中,这些文件可以彼此独立编译,然后链接到同一个最终共享对象中。采用这种方法将有以下好处:
- 减少每个编译单元的内存需求。
- 启用并行构建(如果需要)。
- 允许更快的增量构建。例如,当更改单个类定义时,只有绑定代码通常需要重新编译。
15.7 “recursive template instantiation exceeded maximum depth of 256”
如果得到关于超出递归模板深度的错误,请尝试指定更大的值,例如GCC/Clang
上的-ftemplate-depth=1024
编译标识。其罪魁祸首通常是使用C++14模板元编程在编译时生成函数签名。
15.8 “‘SomeClass’ declared with greater visibility than the type of its field ‘SomeClass::member’ [-Wattributes]”
该错误通常表示在编译时没有使用所需的-fvisibility
标志.pybind11代码从内部强制所有内部代码的隐藏可见性,但如果非隐藏(并因此导出),代码将尝试包括
pybind类型(例如,py::object
或py::list
)可能会遇到此警告。为了避免这种情况,请确保在编译pybind代码时指定-fvisibility=hidden
。
至于为什么-fvisibility=hidden
是必要的,因为pybind
模块可以在pybind
本身的不同版本下编译,同样重要的是,一个模块中定义的符号不会与
在另一个数据库中定义的潜在不兼容符号。虽然Python
扩展模块通常加载本地化的符号(在POSIX
系统下,通常使用带有RTLD_local
标志的dlopen
),但这个Python
默认值
可以改变,但当不使用-fvisibility=hidden
时,即使不改变,也不总是足以保证所涉及符号的完全独立性.
此外,-fvisibility=hidden
可以显著节省二进制大小。(有关详细信息,请参见后续章节.)
15.9 如何创建更小的二进制文件?
为了完成它的工作,pybind11
广泛依赖一种称为模板元编程的编程技术,这是一种在编译时使用类型信息执行计算的方法。模板元编程通常会实例化涉及大量深度嵌套类型的代码,这些类型在编译器的优化阶段要么被完全删除,要么被缩减为仅几条指令。但是,由于这些类型的嵌套性质,编译的扩展库中生成的符号名称可能非常长。例如,包含的测试套件包含以下符号:
__ZN8pybind1112cpp_functionC1Iv8Example2JRNSt3__16vectorINS3_12basic_stringIwNS3_
11char_traitsIwEENS3_9allocatorIwEEEENS8_ISA_EEEEEJNS_4nameENS_7siblingENS_9is_
methodEA28_cEEEMT0_FT_DpT1_EDpRKT2_
这是以下函数类型的展开形式:
pybind11::cpp_function::cpp_function<void, Example2, std::__1::vector<std::__1::basic_
string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >,
std::__1::allocator<std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>,
std::__1::allocator<wchar_t> > > >&, pybind11::name, pybind11::sibling,
pybind11::is_method, char [28]>(void (Example2::*)(std::__1::vector<std::__1::basic_
string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >,
std::__1::allocator<std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>,
std::__1::allocator<wchar_t> > > >&), pybind11::name const&, pybind11::sibling
const&, pybind11::is_method const&, char const (&) [28])
仅存储此函数的错位名称(196 字节)所需的内存大于它所代表的实际代码段(111
字节)!另一方面,甚至给这个函数起个名字都是愚蠢的——毕竟,它只是一个更大的机器中的一个小齿轮,不暴露于外界。因此,我数
-fvisibility=hidden
来实现,它将默认符号可见性设置为隐藏,这对生成的扩展库的最终二进制大小有巨大影响。 (在 Visual Studio
上,默认情况下符号已隐藏,因此无需在此处进行任何操作。)除了减小二进制大小之外,-fvisibility=hidden
还可以避免在加载多个模块时出现潜在的严重问题,并且是正确
pybind们通常只想为那些实际从外部调用的函数导出符号。这可以通过为GCC
和Clang
指定参
操作所必需的。有关更多详细信息,请参阅之前的常见问题解答条目。
15.10 使用古老的基于Windows的Visual Studio 2008
Python
的官方Windows
发行版是使用缺乏良好 C++11
支持的真正古老版本的 Visual Studio
编译的。一些用户隐含地假设不可能将使用 Visual Studio 2015
构建的插件加载到使用 Visual Studio 2008
编译的Python
发行版中。但是,不存在这样的问题:接口使用不同编译器构建的DLL
是完全合法的,并且/或C
库。需要注意的常见问题包括在另一个共享库中使用malloc()
编辑的非free()
内存区域,使用具有不兼容ABI
的数据结构,等等。pybind11
非常小心不要犯这些类型的错误。
15.11 如何在长时间运行的函数中正确处理Ctrl-C
?
Ctrl-C
被Python
解释器接收,并一直保持到GIL
被释放,所以一个长时间运行的函数不会被中断。要从函数内部中断,您可以使用 PyErr_CheckSignals() 函数,该函数将判断 Python
端是否已发出信号。这个函数只检查一个标志,所以它的影响可以忽略不计。接收到信号后,您必须通过抛出 py::error_already_set 显式中断执行(这将传播现有的
KeyboardInterrupt),或者清除错误(您通常不希望这样做):
PYBIND11_MODULE(example, m){
m.def("long running_func",
[](){
for (;;)
{
if (PyErr_CheckSignals() != 0)
throw py::error_already_set();
// Long running iteration
}
});
}
15.12 CMake
未检测到正确的Python
版本
基于CMake
的构建系统将尝试自动检测已安装的Python
版本并与之链接。如果此操作失败,或者有多个版本的Python
并找到错误的版本,请删除CMakeCache.txt
,然后将-DPYTHON_EXECUTABLE=$(which python)
添加到CMake
配置行。(如果您愿意,请将$(which python)
替换为python
的路径。)您也可以尝试-DPYBIND11_FINDPYTHON=ON
,这将激活新的CMake FindPython
支持而不是pybind11
的自定义搜索。需要CMake 3.12+,3.15+
或3.18.2+
更好。您也可以在添加或查找pybind11
之前在CMakeLists.txt
中进行设置。
15.13 CMake
和pybind11
中Python
版本检测不一致
CMake
提供的用于Python
版本检测的函数find_package(PythonInterp)
和find_package(PythonLibs)
被pybind11
修改,原因是它们不适合pybind11
的需要。相反,pybind11
提供了自己的、更可靠的Python
检测CMake
代码。但是,当在安装了多个Python
版本的系统中使用CMake Python
检测的项目中使用pybind11
时,可能会出现冲突。
如果在同一个项目中使用这两种机制,这种差异可能会导致不一致和错误。考虑在安装了Python2.7
和3.x
的系统中执行的以下CMake
代码:
find_package(PythonInterp)
find_package(PythonLibs)
find_package(pybind11)
它将检测Python2.7
,pybind11
也会选择它。
相比之下,这段代码:
find_package(pybind11)
find_package(PythonInterp)
find_package(PythonLibs)
将为pybind11
检测Python3.x
,之后可能会在find_package(PythonLibs)
上崩溃。
有三种可能的解决方案:
- 避免使用
CMake
中的find_package(PythonInterp)
和find_package(PythonLibs)
并依赖pybind11
检测Python
版本。如果这不可能,则应在包含pybind11
之前调用CMake
机器。 - 将
PYBIND11_FINDPYTHON
设置为True
或在现代CMake
上使用find_package(Python COMPONENTS Interpreter Development)
(3.12+,3.15+更好,3.18.2+ 最好)。在这些情况下,Pybind11
使用新的CMake FindPython
而不是旧的、已弃用的搜索工具,并且这些模块在查找正确的Python
方面要好得多。 - 将
PYBIND11_NOPYTHON
设置为TRUE
。Pybind11
不会搜索Python
。但是,您将不得不使用基于目标的系统,并自己进行更多设置,因为它不知道或不包含依赖于Python
的东西,例如pybind11_add_module
。这可能非常适合集成到现有系统中,例如scikit-build
的Python
助手。
15.14 如何引用这个项目?
我们建议使用以下 BibTeX 模板在科学话语中引用 pybind11:
@misc{pybind11,
author = {Wenzel Jakob and Jason Rhinelander and Dean Moldovan},
year = {2017},
note = {https://github.com/pybind/pybind11},
title = {pybind11 -- Seamless operability between C++11 and Python} }