pybind11 学习笔记

news2024/9/20 2:17:29

pybind11 学习笔记

  • 0. 一个例子
  • 1. 官方文档
    • 1.1 Installing the Library
      • 1.1.1 Include as A Submodule
      • 1.1.2 Include with PyPI
      • 1.1.3 Include with Conda-forge
    • 1.2 First Steps
      • 1.2.1 Separate Files
      • 1.2.2 `PYBIND11_MODULE()` 宏
      • 1.2.3 example.`cpython-38-x86_64-linux-gnu`.so 的名称由来
    • 1.3 Build Systems
      • 1.3.1 原始的调用方式
      • 1.3.2 Modules with CMake
        • 1.3.2.1 关于包名 package name
        • 1.3.2.2 关于版本
        • 1.3.2.3 Python 环境设置
        • 1.3.2.4 静态链接与动态链接
      • 1.3.3 其他方式 setuptools 等
        • 1.3.3.1 find_package vs. add_subdirectory

0. 一个例子

如果直接去看官方文档的话, 不熟悉 C/C++ 的初学者可能会一头雾水, 因为它只描述了导入 pybind11 库的方法, 且比较分散(让你点击链接到另一个网页查看), 没有展示一个系统的例子.

故而去 B 站搜索相关演示视频, 找到了《如何在Python中调用C++代码?pybind11极简教程》. 过程如下:

  1. 创建一个名为 VSPyBind11Test 的文件夹, 并在 VS Code 中打开, 为项目的根目录;
  2. 在根目录下创建文件夹 extern, 终端命令进入 extern, 执行命令:
git clone --recursive -b v2.12 --single-branch https://github.com/pybind/pybind11.git
  1. 下载好后, extern 下有一个 pybind11 文件夹, 就是 pybind11 库;
  2. 创建 CMakeLists.txtexample.cpp 文件, 写入内容:
cmake_minimum_required(VERSION 3.10)
project(VSPyBind11Test)

# 可能会需要, 如果配置了环境变量就不需要了
# 或者你想在特定 Python 环境下的编译(还是比较重要的)
# set(PYTHON_EXECUTABLE /root/Miniconda/bin/python)
# set(PYTHON_INCLUDE_DIRS /root/Miniconda/include/python3.8)

add_subdirectory(extern/pybind11)
pybind11_add_module(example example.cpp)
#include <pybind11/pybind11.h>
namespace py = pybind11;

int add(int a, int b)
{
	return a + b;
}

PYBIND11_MODULE(example, m)
{
	m.doc() = "Example module";
	m.def("add", &add, "Add two integers");
}
  1. 在根目录下创建 build 文件夹, 并在终端进入, 执行命令 cmake .., 然后 make, 就会生成一个叫 example.cpython-38-x86_64-linux-gnu.so 的文件, 就是导出的可供 python 调用的包;
  2. build 目录下, 创建 demo.py, 写入 import example; example.add(1, 2), 执行 python demo.py, 则输出 3.

初步注解:

  • add_subdirectory(extern/pybind11) 可能是 C/C++ 开发导入第三方库的一种方法, 有了它就可以在 C/C++ 项目中使用 pybind11 的内容了;
  • PYBIND11_MODULE(example, m) 中, example 是导出的 python 模块名, m 是模块引用, 用来定义模块内容;
  • 从生成的 example.cpython-38-x86_64-linux-gnu.so 命名中可以看到, 它是依赖于 python 环境的, 我的默认 python 确实是 python3.8. 如果在 CMakeLists.txt 中设置 python 环境为 python3.6:
set(PYTHON_EXECUTABLE /root/Miniconda/envs/py36/bin/python)
set(PYTHON_INCLUDE_DIRS /root/Miniconda/envs/py36/include/python3.6)

则得到的文件是 example.cpython-36m-x86_64-linux-gnu.so.

  • 注意, 在 python3.6 下生成的 so 文件不能在 python3.8 下运行, 反之亦然.

在 VS Code 下, 即使你构建了项目, 点开 example.cpp, 可能依然是这样的

重启一下 VS Code 就好了. 而 CLion 下只需要点击 Reload CMake Project 就好了.

1. 官方文档

之后, 再看官方文档会好很多.

1.1 Installing the Library

从 Library 得知, 它就是个库. 官方推荐的安装方式有三种: submodule, PyPI, conda-forge.

1.1.1 Include as A Submodule

就是上面例子中 CMakeLists.txt 文件中的 add_subdirectory(extern/pybind11). 之所以说官方文档不够清楚, 当看到:

时, 试了一下:

# add_subdirectory(extern/pybind11)
include_directories(/extern/ptbind11/include)

根本不行: Unknown CMake command "pybind11_add_module". 点击 see Build systems, 看到一份 CMake 代码:

cmake_minimum_required(VERSION 3.15...3.29)
project(example LANGUAGES CXX)

set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)

pybind11_add_module(example example.cpp)
install(TARGETS example DESTINATION .)

把上面例子中的 CMakeLists.txt 文件内容换成这样, 会报错:

Could not find a package configuration file provided by "pybind11" with any of the following names:
    pybind11Config.cmake
    pybind11-config.cmake

即使在 find_package(...) 中写明路径:

find_package(pybind11 CONFIG REQUIRED PATHS /root/CuProjects/VSPyBind11Test/extern/pybind11)

[小结]: 对于 Include as A Submodule, 暂时只能用 add_subdirectory(extern/pybind11) 了.

1.1.2 Include with PyPI

文档只告诉你可以通过 pip 安装 pybind11:

 pip install pybind11

说这是一个标准的 Python 包格式: This will provide pybind11 in a standard Python package format. 然后, 如果 you want pybind11 available directly in your environment root, you can use:

 pip install "pybind11[global]"

这种安装方式会往 /usr/local/include/pybind11 和 /usr/local/share/cmake/pybind11 中添加文件, 所以官方不推荐这种安装方式. 也就是说, 这样安装 pybind11 可以让你在项目的根目录中直接使用它.

[注意] 最新的 pybind11-v2.13 要求 python >=3.7, 如果是老版本的 python, 请安装 v2.12.

??? 然后呢? 执行完 pip 安装后, 我该如何在 C 项目中导入 pybind11 呢? 文档再一次导向 Build systems. 既然已经安装了, 我们删除 extern/pybind11, 使用 find_package(pybind11 CONFIG REQUIRED), 继续用所给的 CMake 代码, 执行 cmake .. 命令, 和上面一样, 报找不到错误. 那看看下面怎么说:

呃, 看不懂了, 不知道这个 pyproject.toml 是个啥文件, 从文件名和上下文看, 它应该是一个构建 Python 包所需要的配置文件, 为了确认它是什么, 以及它和上面的 CMakeLists.txt 文件是什么关系, 先问问通义千问: [pyproject.toml 是什么?]

pyproject.toml 是一个用于 Python 项目的配置文件,它主要用于存储与构建系统和依赖管理相关的元数据。这个文件格式最初是为了简化 Python 包的构建过程而引入的,现在已经被广泛接受,并成为 PEP 518 和 PEP 517 标准的一部分。这些标准定义了如何使用现代工具来构建 Python 包,而无需依赖传统的 setup.py 文件

那么这样就清楚了, 它跟 pybind11 似乎关系不大, 这里可能就是为了说明如何构建 Python 包, 而不是说明如何在 C 项目中导入 pybind11. 继续往下面看, Modules with meson-python, Modules with setuptools, … 都在讲如何构建 Python 包. 那么至此, 难以找到 “pip install pybind11 后如何导入到 C 项目中” 的答案. 耗费了一天时间, 试了很多方案, 都没能解决.

不过功夫不负有心人, 迷迷糊糊试了一下:

find_package(pybind11 CONFIG REQUIRED PATHS /root/Miniconda/lib/python3.8/site-packages/pybind11/share/cmake/pybind11)

这个路径是前面报错中所说的找不到的 pybind11Config.cmake, pybind11-config.cmake 两个文件的路径, 而 /root/Miniconda/lib/python3.8/site-packages/pybind11pybind11 的 pip 安装路径. 竟然成功地导入了. 可以正常地将 C 语言导出 Python 接口了.

于是想, 何必找那么细呢? 只给出 pybind11 的安装路径不行吗? 于是:

find_package(pybind11 CONFIG REQUIRED PATHS /root/Miniconda/lib/python3.8/site-packages/pybind11)
find_package(pybind11 CONFIG REQUIRED PATHS /root/Miniconda/lib/python3.8/site-packages)

都是可以的. 奇了怪了, 当初 find_package(pybind11 CONFIG REQUIRED PATHS extern/pybind11) 咋就不行呢? 后来还是被我发现了, 后面会讲.

1.1.3 Include with Conda-forge

虽然不太清楚 conda 安装和 pip 安装的具体区别, 但猜测这种方式和 pip 应该是很类似的:

conda install -c conda-forge pybind11

文档只给了这么多. 为了测试这种安装方式, 在执行 conda 安装之前先卸载掉 pip 安装的 pybind11:

pip uninstall pybind11

卸载掉之后的, 发现重新构建 CMake 项目就找不到 pybind11 了. conda 安装 pybind11 之后, 再试试 cmake .., 哎! 成功了! 重磅! 用 conda 安装的 pybind11, 不用指定路径, 只需要:

find_package(pybind11 CONFIG REQUIRED)

这可能是因为用 conda 安装时, /root/Miniconda/include/root/Miniconda/share/cmake 目录下均出现了 pybind11 文件夹, 其中 /root/Miniconda/share/cmake/pybind11 中有那两个之前找不到的文件. 甚至, 我在 conda 的 bin 目录下发现了一个叫 pybind11-config 的文件, 在终端执行:

pybind11-config --version

能输出:

2.13.5

也就是说, conda 安装 pybind11 时配置好了路径. 怎么配置的? 终端执行:

pybind11-config -h
# >>>>>>>>>>>> output >>>>>>>>>>>>
usage: pybind11-config [-h] [--version] [--includes] [--cmakedir] [--pkgconfigdir]
optional arguments:
  -h, --help      show this help message and exit
  --version       Print the version and exit.
  --includes      Include flags for both pybind11 and Python headers.
  --cmakedir      Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.
  --pkgconfigdir  Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.
pybind11-config --includes
-I/root/Miniconda/include/python3.8 -I/root/Miniconda/lib/python3.8/site-packages/pybind11/include

pybind11-config --cmakedir
/root/Miniconda/lib/python3.8/site-packages/pybind11/share/cmake/pybind11

pybind11-config --pkgconfigdir
/root/Miniconda/lib/python3.8/site-packages/pybind11/share/pkgconfig

配置原来都在这, 难怪能直接 find_package(...). 但是, 如果你在 CLion 中构建 CMake 项目, 似乎还是会报找不到 pybind11 的错误.

conda 不仅仅是一个 python 管理工具!!!

  • 记得当初需要用 R 语言处理一些数据时, 就有人说能用 conda 安装 R 语言, 我尝试了一下, 还是不错的;
  • 在打开 scikit-build-core 的网页时, 发现很多程序已经用 scikit-build-core 构建并发布到 PyPI 上了, cmake 和 ninja 赫然在列, 能用 pip 安装, 那可能也能用 conda 安装, 终端执行 conda install cmake, 果然安装了 cmake-3.26.4, 虽然 PyPI 上已经 3.30.3 了.
  • 终端运行 cmake --version, 输出 3.26.4; 执行 conda deactivate 回到非 conda 环境, 输出 3.10.2, 是系统上的 cmake 版本; 在其他 conda 环境(如我的 py36)下, 也是 3.10.2. 所以, conda 安装的软件是依赖于当前环境的.

[小结]: conda 环境更像是一个镶嵌在操作系统上的虚拟系统, 它有自己的 bin, include, lib, sbin, share, 安装软件时很像 apt; 它是独立的, 又能访问宿主系统.

有了上面的分析, 可能的原因就是我们在终端或者 VS Code 中构建 CMake 项目时, 用的是 conda 的 base 环境, 而 CLion 不是. 执行 conda deactivate 回到系统环境, 再执行 cmake .., 果然找不到 pybind11 了. 现在, 我的 base 环境下是有 cmake-3.26.4 的, 设置 CLion 的 cmake 为 /root/Miniconda/bin/cmake, 果然又行得通了; 在系统的非 conda 终端中执行 /root/Miniconda/bin/cmake .. 也行得通. 验证了 conda 环境像是一个镶嵌在操作系统上的虚拟系统的说法.

至此, 安装和导入部分算是完成了!

1.2 First Steps

这一节没什么好说的, 基本和第 0 节的例子差不多. 值得一提的是:

也就是说, <pybind11/pybind11.h> 要写在第一行.

1.2.1 Separate Files

In practice, implementation and binding code will generally be located in separate files.
既然文档提了这一句, 那就试试看:

VSPyBind11Test/
|----CMakeLists.txt
|----build/
|----src/
|    |----example.cpp
|    |----add/
|    |    |----add.h
|    |    |----add.cpp
|    |----sub/
|    |    |----sub.h
|    |    |----sub.cpp
#include <pybind11/pybind11.h>
#include "add.h"
#include "sub.h"

namespace py = pybind11;

PYBIND11_MODULE(example, m)
{
	m.doc() = "Example module";
	m.def("add", &add, "Add two integers");
	m.def("sub", &sub, "Sub two integers");
}

编辑 CMakeLists.txt:

cmake_minimum_required(VERSION 3.10...3.29)
project(example LANGUAGES CXX)

include_directories(src/add src/sub)

set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)

add_library(add SHARED src/add/add.cpp)
add_library(sub SHARED src/sub/sub.cpp)

pybind11_add_module(example src/example.cpp)
target_link_libraries(example PRIVATE add sub)

install(TARGETS example DESTINATION .)

则完成了多文件的链接编译. 这样的话, 如果需要导出现有的 C 函数, 只需要编写一个类似 example.cpp 的文件, 然后进行链接就可以了.

在这个 CMakeLists.txt 文件中, 你也可以这样写:

...
find_package(pybind11 CONFIG REQUIRED)
pybind11_add_module(example src/example.cpp src/add/add.cpp src/sub/sub.cpp)
install(TARGETS example DESTINATION .)

1.2.2 PYBIND11_MODULE()

PYBIND11_MODULE(example, m)
{
	m.doc() = "Example module";
	m.def("add", &add, "Add two integers");
	m.def("sub", &sub, "Sub two integers");
}

上面代码中的 PYBIND11_MODULE() 是一个宏, 接收两个参数, examplemodule name, 它大概应该和 example.cpp 一致, 试了一下 PYBIND11_MODULE(example1, m), 会发现 import example, import example1 都报错. m 是一个指向 example 模块的 py::module_ 类型的变量, 它是可变的, 如 mm 也可以.

关于此宏的其他功能, 可见官方文档, 这里就不多说了.

1.2.3 example.cpython-38-x86_64-linux-gnu.so 的名称由来

文档中还提到:

$ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)

这是手动编译的命令, 执行 python-config --extension-suffix, 会根据你的 python 解释器和系统得到:

.cpython-38-x86_64-linux-gnu.so

Python 解释器是可以设置的, CLion 中, 只要在设置中指定好解释器就好了. 当然也可以在 CMakeLists.txt 中直接设置:

set(PYTHON_EXECUTABLE /root/Miniconda/bin/python)

关于这一点, 上面的例子已经提过了.

1.3 Build Systems

当明白上面讲的内容后, 就会发现文档的这一节是在教你如何在使用 pybind11 导出 C 程序的 Python 接口的情况下, 构建 Python 包. 有多种方式, 包括 Modules with CMake, Modules with meson-python, Modules with setuptools, Building with cppimport, Building with CMake, …
(可以先了解一下 Python 包的构建, 前面关于 pyproject.toml 的事就清楚了.)

1.3.1 原始的调用方式

按理说, 像前面那样构建好 .so 文件就够了, 已经可以在 python 脚本中进行导入和调用:

import example
print(example.add(1, 2))  # 3

可移植性又好, 想在哪里调用就直接 cmake 一下. 因为在一个环境下编译的 .so 在另一个环境不一定可用.

不好的地方在于, 开发环境 IDE 会提示你找不到 example, 因为这里没有指导 IDE 进行代码检查的 Python 代码信息. 解决办法是靠 example.pyi, 它对代码的执行不产生任何影响, 只对 IDE 的代码检查及文档说明提供帮助:

def add(x: int, y: int) -> int:
	"""
	Add two integers
	:param x: The first integer
	:param y: The second integer
	:return: The result of x + y
	"""
	...

def sub(x: int, y: int) -> int:
	"""
	Integer Subtraction Computation
	:param x: The first integer
	:param y: The second integer
	:return: The result of x - y
	"""
	...

这样, 不光 IDE 不报错, 还能提供漂亮的文档提示.

1.3.2 Modules with CMake

有了对 Python 包构建 的了解, 就彻底明白 pyproject.toml 是怎么回事:

[build-system]  # 使用 scikit-build-core 后端构建 python 包
requires = ["scikit-build-core", "pybind11"]
build-backend = "scikit_build_core.build"

[project]
name = "example"
version = "0.1.0"

但我现在只知道官方给的 Python 包构建的例子, 并不知道 scikit-build-core 作为后端时, 怎样将 CMake 项目构建成 Python 包. 于是, 跟着导航, 导向 scikit_build_example 看一看:

scikit_build_example/
├── LICENSE
├── README.md
├── CMakeLists.txt
├── pyproject.toml
├── src/
│   ├── main.cpp
│   └── scikit_build_example/
│       └── __init__.py
└── tests/

项目的目录结构(忽略了一些可选部分), 和 Packaging Python Projects 中给的例子差不多, 多了个 CMakeLists.txt, example.py 换成了 main.cpp. 具体内容就不列出来了, 请参考 Github. 同样是执行:

pip install .
# or
python -m build

就构建好了, 安装之后, 就可以正常使用了.

1.3.2.1 关于包名 package name

pyproject.toml 中的

[project]
# 生成的包名, pip 安装时使用的包名(xxx.dist-info), 但 import 时需要使用模块名(安装的代码所在地)
name = "example"  # 本来是 scikit_build_example, 为了研究包名, 更改为 example

是指 pip 安装时的名称, 也就是 pip install . 后, 你如果要卸载, 就要用这个名字, 因为 site-packages 下, 包信息的文件夹是 example.dist-info; 它也是你执行 python -m build 时, 生成的 whl 包的名字.

install(TARGETS _core DESTINATION cmake_example)  # .so 文件所在地

这是 CMakeLists.txt 中的安装语句, 表示生成的 _core.so 文件会安装到 site-packages 文件夹下的哪里, 这里会安装到 cmake_example 文件夹下.

src/
├── main.cpp
└── scikit_build_example/  # 这个名字要跟 name = "..." 中的一致, 不然这个 __init__.py 不会被安装
    └── __init__.py

我估计是有了 name = "example" 之后, 构建工具会去 src寻找同名的 Python 包, 把它复制到 conda 环境的 site-packages. 而 C 包 xxx.so 是 Python 包所需要的, 所以, CMakeLists.txt 中的 DESTINATION 要考虑为: Python 包需要我在哪里!

1.3.2.2 关于版本
project(
	${SKBUILD_PROJECT_NAME}
	VERSION ${SKBUILD_PROJECT_VERSION}
	LANGUAGES CXX
)

其中的 SKBUILD_PROJECT_NAME 变量是

[project]
name = "example"
version = "0.0.1"

当你手动执行 cmake .. 时, 这两个值似乎都是空的, 当 python -m build 时, 应该是 scikit-build-core 执行的 cmake 命令, 并name = "example", version = "0.0.1" 传递给了 CMake.

CMake 获取版本信息后:

target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION})

中又有版本信息, 这应该是传递给 cpp 代码的, 因为我发现:

#ifdef VERSION_INFO
    m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO);
#else
    m.attr("__version__") = "dev";
#endif

在检测宏 VERSION_INFO, 正是 CMake 中的 VERSION_INFO, 把它删掉:

target_compile_definitions(_core PRIVATE)

执行 example.__version__ 就输出 'dev'. 设置成:

target_compile_definitions(_core PRIVATE VERSION_INFO=0.2.0)

执行 example.__version__ 就输出 '0.2.0'.

1.3.2.3 Python 环境设置

在 CMakeLists.txt 中, 可能使用以下语句设置 Python:

set(PYTHON_BASE_PATH /root/Miniconda/envs/xxx)
set(PYTHON_INCLUDE_DIRS ${PYTHON_BASE_PATH}/include/python3.8)
set(PYTHON_LIBRARY ${PYTHON_BASE_PATH}/lib/libpython3.8.so)
set(PYTHON_EXECUTABLE ${PYTHON_BASE_PATH}/bin/python)

但似乎这样更简单:

set(PYTHON_BASE_PATH /root/Miniconda/envs/xxx)
find_package(Python REQUIRED COMPONENTS Interpreter Development.Module PATHS ${PYTHON_BASE_PATH})

# Interpreter 对应了
# set(PYTHON_EXECUTABLE ${PYTHON_BASE_PATH}/bin/python)
# 还能获取 Python_VERSION
# Development.Module 对应了
# set(PYTHON_INCLUDE_DIRS ${PYTHON_BASE_PATH}/include/python3.x)
# set(PYTHON_LIBRARY ${PYTHON_BASE_PATH}/lib/libpython3.x.so)

注意这个需要 CMake-3.12+(3.15+ recommended, 3.18.2+ ideal).

1.3.2.4 静态链接与动态链接

问题来了: 当按照 1.2.1 Separate Files 中的文件结构时, 安装 Python 包后就会报错:

ImportError: libadd.so: cannot open shared object file: No such file or directory

而 1.2.1 中的手动编译, 不打包 Python 包就不报错. 检查了一下安装后的包解构, 也没啥问题:

example/
├── __init__.py
├── _core.cpython-38-x86_64-linux-gnu.so
├── libadd.so
├── libsub.so
└── __pycache__/
    └── __init__.cpython-38.pyc

到底咋回事, cd 到包里面, 执行:

(base) root@kklt:~/CuProjects/CLionPyBind11Test# cd ~/Miniconda/lib/python3.8/site-packages/example/
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# ls
__init__.py  __pycache__  _core.cpython-38-x86_64-linux-gnu.so  libadd.so  libsub.so
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# python
Python 3.8.18 (default, Sep 11 2023, 13:40:15) 
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import _core
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: libadd.so: cannot open shared object file: No such file or directory

在包内调用也不行; 把自己编译的 _core.cpython-38-x86_64-linux-gnu.so 移动到 example 内:

(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# cp ~/CuProjects/CLionPyBind11Test/cmake-build-debug/_core.cpython-38-x86_64-linux-gnu.so ./
(base) root@kklt:~/Miniconda/lib/python3.8/site-packages/example# python
Python 3.8.18 (default, Sep 11 2023, 13:40:15) 
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import _core
>>> _core.add(1, 2)
3

甚至 example 包也能正常导入了:

(base) root@kklt:~# python
Python 3.8.18 (default, Sep 11 2023, 13:40:15)
[GCC 11.2.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> example.add(1, 2)
3

不清楚什么原因, 可能是 Python 包构建工具经过了什么特殊处理导致的. 还有一种解决办法:

add_library(add STATIC src/add_module/add.cpp)
add_library(sub STATIC src/sub_module/sub.cpp)

SHARED 换成 STATIC 也可以, 此时甚至不需要 labadd.solabadd.so 了. 问了通义千问:

对于 ImportError: libadd.so: cannot open shared object file: No such file or directory, 我把 add_library(add SHARED src/add/add.cpp) 改成 add_library(add STATIC src/add/add.cpp) 就解决了错误, 这是为什么?

你遇到的 ImportError: libadd.so: cannot open shared object file: No such file or directory 错误通常是因为 Python 在运行时找不到必要的共享库。当你将 add_library(add SHARED src/add/add.cpp) 改为 add_library(add STATIC src/add/add.cpp) 后,问题得到了解决,原因在于静态库和共享库在链接时的行为不同。
=> 共享库 (SHARED)
当使用 add_library(… SHARED …) 创建共享库时,生成的库文件(如 libadd.so)是一个独立的动态链接库。这意味着:
---- 动态链接:在编译时,链接器不会将库文件中的符号(函数和变量)嵌入到最终的可执行文件或库中。相反,它会在运行时查找这些符号
---- 运行时依赖:运行时,Python 解释器需要找到并加载这个共享库。因此,共享库必须存在于 Python 解释器可以找到的地方(通常是 LD_LIBRARY_PATH 或 /etc/ld.so.conf 指定的路径)。
=> 静态库 (STATIC)
当使用 add_library(… STATIC …) 创建静态库时,生成的库文件(如 libadd.a)包含了所有必要的符号。这意味着:
---- 静态链接:在编译时,链接器会将静态库中的符号嵌入到最终的可执行文件或库中。这样做的结果是最终的可执行文件或库包含了静态库中的所有符号。
---- 无需运行时依赖:由于所有的符号都已经嵌入到了最终的可可执行文件或库中,因此在运行时不需要再查找和加载静态库

但, 终究是不知道为啥构建的 Python 包会找不到就在跟前的 libadd.so.

1.3.3 其他方式 setuptools 等

构建 Python 包的方式还有很多, 且似乎大有不同, 比如 setuptools 安装的包似乎找不到 .so 文件在哪, 且构建文件目录中竟然没有 CMakeLists.txt 问价, 故而不明白 C 项目是如何构建的.

内容太多且用不到, 只作为大概了解, 故而就不进一步追究了. 但在下文中还是看到了一点东西可以解答前面的疑惑:

1.3.3.1 find_package vs. add_subdirectory

对于基于 CMake 的 C 项目, 没有内部包含 pybind11 repository, 所以要进行 external installation, 再通过 find_package(pybind11) 查找.

find_package(pybind11 REQUIRED)
pybind11_add_module(example example.cpp)

注意: find_package(pybind11) 只有在 pybind11 被正确地安装到系统中后才能正确工作, 即, 下载或克隆后:

# Classic CMake
cd pybind11
mkdir build
cd build
cmake ..
make install

这是常规的安装软件的步骤, 所以, 前面说的"用 find_package(...) 替换 add_subdirectory"不工作.

找到了 pybind11 包之后, 前面提到的 pybind11_add_module 就可以使用了. 当你定义了 PYBIND11_FINDPYTHON:

set(PYBIND11_FINDPYTHON ON)

或者执行 cmake 命令行时加上: DPYBIND11_FINDPYTHON=ON, 那么 pybind11 会自动为你执行 FindPython 操作.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2147610.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轴承表面缺陷检测系统源码分享

轴承表面缺陷检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

GUI编程17:下拉框、列表框

视频链接&#xff1a;19、下拉框、列表框_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p19&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.下拉框 代码示例 package com.yundait.lesson06;import javax.swing.*; import java.awt.*;public class Te…

Docker实践——天池篇

参考零基础入门Docker-cuda练习场_学习赛_天池大赛-阿里云天池的赛制 (aliyun.com) ​ 在Docker零基础入门-CSDN博客中我已经安装了docker,现在开始创建自己的镜像仓库。 1. 开通阿里云容器镜像服务(镜像仓库) 进入容器镜像服务 (aliyun.com) 1.1. 创建个人实例 点击“…

Vue | watch监听

Vue | watch监听 在Vue.js的世界里&#xff0c;watch监听器是一个强大且灵活的工具&#xff0c;它允许我们在数据变化时执行特定的逻辑。本文将深入探讨watch的出现背景、使用方法、应用场景、源码原理以及扩展技巧&#xff0c;旨在帮助读者全面掌握这一重要特性。 文章目录 Vu…

JavaEE---Spring IOC(2)

DI之三种注入 属性注入 构造方法注入 Setter注入 当程序中同一个类有多个对象的时候会报错解决方法如下: AutoWired和Resource的区别

C8T6--SPI读FLASH和双通信

C8T6–SPI读取FLASH和双通信 本小节以一种使用 SPI 通讯的串行 FLASH 存储芯片的读写实验为大家讲解 STM32 的 SPI 使用方法。实验中 STM32 的 SPI 外设采用主模式&#xff0c;通过查询事件的方式来确保正常通讯 大纲 SPI读取FLASH双SPI接口进行主从相互通信 具体案例 SPI…

SSC377/D, 5M30 64/128MB, 1Tops1. 支持双摄,甚至三摄;2. 夜视全彩;3. 省内存、省带宽;4. 算力较大,适合新的算法模型;

 High Performance Processor Core  ARM Cortex-A35  Clock rate up to 1.0 GHz  Neon and FPU  Memory Management Unit for Linux support  DMA Engine  Image/Video Processor  Supports 8/10/12-bit parallel interface for raw data inpu…

【算法基础实验】图论-BellmanFord最短路径

理论知识 Bellman-Ford 和 Dijkstra 是两种用于计算加权图中最短路径的算法&#xff0c;它们在多个方面存在不同之处。下面是它们之间的主要区别&#xff1a; 1. 边权重的处理 Bellman-Ford&#xff1a; 能够处理带有负权重边的图&#xff0c;且可以检测负权重环&#xff08…

chapter16-坦克大战【1】——(自定义泛型)——day21

目录 569-坦克大战介绍 570-JAVA坐标体系 571-绘图入门和机制 572-绘图方法 573-绘制坦克游戏区域 574-绘制坦克 575-小球移动案例 576-事件处理机制 569-坦克大战介绍 570-JAVA坐标体系 571-绘图入门和机制 572-绘图方法 573-绘制坦克游戏区域 574-绘制坦克 575-小球移…

硬件工程师笔试面试——保险丝

目录 10、保险丝 10.1 基础 保险丝原理图 保险丝实物图 10.1.1 概念 10.1.2 保险丝的工作原理 10.1.3 保险丝的主要类型 10.1.4 保险丝的选择和使用注意事项 10.2 相关问题 10.2.1 保险丝的额定电流和额定电压是如何确定的? 10.2.2 保险丝的熔断速度对电路保护有何…

二进制补码及与原码的互相转换方法-成都仪器定制

大沙把一些基础的知识说清楚&#xff0c;本文介绍二进制补码及与原码的转换方法。 先说原码&#xff0c;原码‌是一种计算机中对数字的二进制定点表示方法。在原码表示法中&#xff0c;数值前面增加了一位符号位&#xff0c;最高位为符号位&#xff0c;0表示正数&#xff0c;1表…

keil调试变量值被篡改问题

今天遇到一个代码中变量值被篡改的问题&#xff0c;某个数组的第一个值运行一段时间之后变成了0&#xff0c;如图&#xff1a; 看现象基本可以断定是内存越界导致的&#xff0c;但是要如果定位是哪里内存越界呢? keil提供了两个工具 1、set access breakpoint at(设置访问断点…

项目小总结

这段时间主要把大概的开发流程了解完毕 修改了&#xff0c;并画了几个界面 一.界面 修改为 博客主页 个人中心 二.前后端分离开发 写前端时 就可以假设拿到这些数据了 const blogData2 {blog:{id:1,title: "如何编程飞人",author_id: 1,content: "这是一篇…

数据结构之二叉树遍历

二叉树的遍历 先序遍历 先输入父节点&#xff0c;再遍历左子树和右子树&#xff1a;A、B、D、E、C、F、G 中序遍历 先遍历左子树&#xff0c;再输出父节点&#xff0c;再遍历右子树&#xff1a;D、B、E、A、F、C、G 后序遍历 先遍历左子树&#xff0c;再遍历右子树&#xff0c;…

爬虫框架之Scrapy介绍——高效方便

# 近年来大数据分析、数据可视化和python等课程逐渐在大学各个学科中铺展开来&#xff0c;这样一来爬虫在平时小作业和期中、期末报告中出现的频率也逐渐变高。那么单一的使用requests库&#xff0c;自己从头到尾的的设计&#xff0c;考虑数据提取、线程管理和数据存储等方方面…

微服务架构详解

微服务与SOA概述 SOA历史 SOA示例 微服务历史 SOA 被抛弃了么? 微服务与 SOA 剖析 SOA 架构剖析 ESB就是一个一个微服务的功能 ESB 功能举例 对象转换还有逻辑转换 很多东西都要在ESB里面处理 微服务剖析 把一个单体结构拆分多个小服务。为了让小服务之间通信方便&#x…

用AI的智慧,传递感恩之心——GPT-4o助力教师节祝福

随着科技的飞速发展&#xff0c;人工智能在我们生活中的应用日益广泛。在这个教师节&#xff0c;不仅可以用传统的方式表达对老师的感恩之情&#xff0c;还可以借助OpenAI最新推出的GPT-4o模型&#xff0c;生成独特而温暖的祝福语和精美海报&#xff0c;让我们的感恩显得更加与…

Renesas R7FA8D1BH (Cortex®-M85)的UART使用介绍

目录 概述 1 软硬件 1.1 软硬件环境信息 1.2 开发板信息 1.3 调试器信息 2 FSP配置UART 2.1 配置参数 2.2 UART模块介绍 3 接口函数介绍 3.1 R_SCI_B_UART_Open() 3.2 R_SCI_B_UART_Close() 3.3 R_SCI_B_UART_Read() 3.4 R_SCI_B_UART_Write() 3.5 R_SCI_B_UAR…

【iOS】——JSONModel源码

JSONModel用法 基本用法 将传入的字典转换成模型&#xff1a; 首先定义模型类&#xff1a; interface Person : JSONModel property (nonatomic, copy) NSString *name; property (nonatomic, copy) NSString *sex; property (nonatomic, assign) NSInteger age; end接…

Java 23 的12 个新特性!!

Java 23 来啦&#xff01;和 Java 22 一样&#xff0c;这也是一个非 LTS&#xff08;长期支持&#xff09;版本&#xff0c;Oracle 仅提供六个月的支持。下一个长期支持版是 Java 25&#xff0c;预计明年 9 月份发布。 Java 23 一共有 12 个新特性&#xff01; 有同学表示&…