目录

最后决定选用pybind11,理由如下:

  1. 比python原生的C API看起来人性多了
  2. 我的C++代码不是现成的,需要一定的C++开发工作量,所以感觉cython不是很方便。如果C++接口已经给好了,只需要简单包装一下,Cython可能更好。
  3. pybind11声称只包含头文件,且能通过pip安装,感觉比boost_python轻量且最后这个扩展包容易分发。此外,感觉它的文档也比boost python友好不少……

Setuptools

参考官方的Setuptools构建文档

这种方式适合python包的构建、打包、分发、上传到PyPi一条龙服务。python使用C++扩展需要在setup.py里配置好Extension。以下是一个setup.py的样例:

import glob
import os.path
from distutils.core import setup __version__ = "0.0.1" # make sure the working directory is BASE_DIR
BASE_DIR = os.path.dirname(__file__)
os.chdir(BASE_DIR) ext_modules = [] try:
from pybind11.setup_helpers import Pybind11Extension, ParallelCompile, naive_recompile # `N` is to set the bumer of threads
# `naive_recompile` makes it recompile only if the source file changes. It does not check header files!
ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile, default=4).install() # could only be relative paths, otherwise the `build` command would fail if you use a MANIFEST.in to distribute your package
# only source files (.cpp, .c, .cc) are needed
source_files = glob.glob('source/path/*.cpp', recursive=True) # If any libraries are used, e.g. libabc.so
include_dirs = ["INCLUDE_DIR"]
library_dirs = ["LINK_DIR"]
# (optional) if the library is not in the dir like `/usr/lib/`
# either to add its dir to `runtime_library_dirs` or to the env variable "LD_LIBRARY_PATH"
# MUST be absolute path
runtime_library_dirs = [os.path.abspath("LINK_DIR")]
libraries = ["abc"] ext_modules = [
Pybind11Extension(
"package.this_package", # depends on the structure of your package
source_files,
# Example: passing in the version to the compiled code
define_macros=[('VERSION_INFO', __version__)],
include_dirs=include_dirs,
library_dirs=library_dirs,
runtime_library_dirs=runtime_library_dirs,
libraries=libraries,
cxx_std=14,
language='c++'
),
]
except ImportError:
pass setup(
name='project_name', # used by `pip install`
version='0.0.1',
description='xxx',
ext_modules=ext_modules,
packages=['package'], # the directory would be installed to site-packages
setup_requires=["pybind11"],
install_requires=["pybind11"],
python_requires='>=3.8',
include_package_data=True,
zip_safe=False,
)

一些需要注意的点(坑):

  • 如果需要通过sdist(即.tar.gz的源码方式)发布包的话,Extensionsource_files字段必须是相对路径。否则build的时候会因为egg-info里的SOURCE.txt里有绝对路径而报错。但由此带来的问题是我们不能确定跑setup.py的时候工作目录是啥,为了保险起见,需要把它设置成setup.py所在的目录

  • 在安装包之前,为了获取一些metadata,setuptools会先跑一次setup.py,这个时候如果没有装pybind11,会报错。为了解决这个问题:

    • 为了能正常执行到setup函数,我们需要先保证没有pybind11的情况下执行这个文件也不会报错。所以我们需要把所有依赖pybind11.setup_helpers的部分都放到try里。

      也有其它的方法,比如直接复制一个setup_helpers啥的,具体可以看文档。

    • 根据setuptools的文档setup_requires并不会安装包,所以pybind11也需要加到install_requires里。
    • 最后,在安装本包前,setuptools会先安装依赖项,然后再跑setup.py install,这时就可以成功build和安装了。
  • 如果你的外接库不在系统查找动态库的指定路径里,那么指定link_dirs之后,编译和链接不会出错。但执行的时候还是会因为找不到动态库而报错。可以通过添加runtime_library_dirs(等价于-Wl,-rpath),或者给LD_LIBRARY_PATH环境变量里添加这个路径。

  • 编译后的.so的位置,以及你的C++ module在python里的名字,取决于你给Extension写的名字。例如,你希望文件结构是这样:

    project_dir
    |-- package
    | |-- __init__.py
    | |-- this_package.xxxx.so
    | |-- other.py
    |-- setup.py

    这样你最后在site-packages里只会新建一个包叫package

    此外,哪怕你这个project只想导出一个.so里的模块,把它放到一个文件夹里包装起来也会更好。因为如果你只想导出一个this_package,把setup函数里的配置改成了packages=['this_package'],这个.so文件会直接被加到site-packages,感觉不是很优雅。

    这时候你的Extension的名字就需要是package.this_package。这样.so的位置就是对的,你运行import package.this_package就会正确地找到.so并执行了

    但是,要保证执行.so不出错,在C++里通过PYBIND11_MODULE把这个扩展expose到python里的时候,名字也要对应

    PYBIND11_MODULE(_pynodejs, m) {}

CMake

参考官方的CMake构建文档

如果是编译嵌入python的C++程序,可以用CMake,比较方便。

虽然python extension似乎也可以用CMake,但是还是setuptools比较方便。

我这里主要是用CMake编译C++部分的测试。CMakeLists.txt大概长这样:

# the CMakeList to test the C++ part from a C entry point
cmake_minimum_required(VERSION 3.21)
project(project_name) set(CMAKE_CXX_STANDARD 14) # Find pybind11
find_package(pybind11 REQUIRED) # If any library (e.g. libabc.so) is needed
include_directories(INCLUDE_DIR)
link_directories(LINK_DIR) # Add source file
file(GLOB A_NAME_FOR_SOURCE CONFIGURE_DEPENDS "source/path/*.cpp")
file(GLOB A_NAME_FOR_TEST CONFIGURE_DEPENDS "test/path/*.cpp")
add_executable(TARGET_NAME test_cpp_part.cpp ${A_NAME_FOR_SOURCE} ${A_NAME_FOR_TEST})
target_link_libraries(TARGET_NAME abc pybind11::embed)
  • project_name随便写
  • TARGET_NAME随便写,只要add_executabletarget_link_libraries对应就行,是最后的可执行文件的名字
  • A_NAME_FOR_SOURCEA_NAME_FOR_TEST是一个CMake的中间变量名,随便写,它们分别代表了GLOB找到的一堆源文件,和用于测试的一堆文件
  • INCLUDE_DIR里是库abc的头文件,LINK_DIR里必须包含库文件,动态库类似libabc.so,静态库类似libabc.a
  • Link到pybind11::embed的原因是防止带python对象的那部分C++代码编译失败。

使用pybind11为Python编写C++扩展(一)配置篇:Build(编译和链接)的更多相关文章

  1. 最全总结 | 聊聊 Python 数据处理全家桶(配置篇)

    1.前言 在实际项目中,经常会接触到各种各样的配置文件,它可以增强项目的可维护性 常用配件文件的处理方式,包含:JSON.ini / config.YAML.XML 等 本篇文章,我们将聊聊 Pyth ...

  2. Python和C扩展实现方法

    一.Python和C扩展 cPython是C编写的,python的扩展可以用C来写,也便于移植到C++. 编写的Python扩展,需要编译成一个.so的共享库. Python程序中. 官方文档:htt ...

  3. python编写shell脚本详细讲解

    python编写shell脚本详细讲解 那,python可以做shell脚本吗? 首先介绍一个函数: os.system(command) 这个函数可以调用shell运行命令行command并且返回它 ...

  4. 用Python编写一个简单的Http Server

    用Python编写一个简单的Http Server Python内置了支持HTTP协议的模块,我们可以用来开发单机版功能较少的Web服务器.Python支持该功能的实现模块是BaseFTTPServe ...

  5. 为Python编写一个简单的C语言扩展模块

    最近在看pytorh方面的东西,不得不承认现在这个东西比较火,有些小好奇,下载了代码发现其中计算部分基本都是C++写的,这真是要我对这个所谓Python语音编写的框架或者说是库感觉到一丢丢的小失落,细 ...

  6. python使用C扩展

    CPython还为开发者实现了一个有趣的特性,使用Python可以轻松调用C代码 开发者有三种方法可以在自己的Python代码中来调用C编写的函数-ctypes,SWIG,Python/C API.每 ...

  7. 使用由 Python 编写的 lxml 实现高性能 XML 解析

    lxml 简介 Python 从来不出现 XML 库短缺的情况.从 2.0 版本开始,它就附带了 xml.dom.minidom 和相关的 pulldom 以及 Simple API for XML ...

  8. 基于python编写的天气抓取程序

    以前一直使用中国天气网的天气预报组件都挺好,可是自从他们升级组件后数据加载变得非常不稳定,因为JS的阻塞常常导致网站打开速度很慢.为了解决这个问题决定现学现用python编写一个抓取程序,每天定时抓取 ...

  9. 用Python编写博客导出工具

    用Python编写博客导出工具 罗朝辉 (http://kesalin.github.io/) CC 许可,转载请注明出处   写在前面的话 我在 github 上用 octopress 搭建了个人博 ...

随机推荐

  1. 【LeetCode】367. Valid Perfect Square 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 方法一:完全平方式性质 方法二:暴力求解 方法三:二 ...

  2. 【LeetCode】106. Construct Binary Tree from Inorder and Postorder Traversal 解题报告

    [LeetCode]106. Construct Binary Tree from Inorder and Postorder Traversal 解题报告(Python) 标签: LeetCode ...

  3. LeetCode1238循环码排列

    题目 给你两个整数 n 和 start.你的任务是返回任意 (0,1,2,,...,2n-1) 的排列 p,并且满足: p[0] = start p[i] 和 p[i+1] 的二进制表示形式只有一位不 ...

  4. 分析一个简单的goroutine资源池

    分析一个简单的goroutine资源池 tunny. 从资源池中获取goroutine并进行处理的逻辑如下: tunny将goroutine处理单元封装为workWrapper,由此可以对gorout ...

  5. webservice websocket socket http rpc

    HTTP: HTTP 是基于请求响应式的,即通信只能由客户端发起,服务端做出响应,无状态,无连接. webSocket: 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向 ...

  6. Visualizing Data using t-SNE

    目录 概 主要内容 Stochastic Neighbor Embedding t-SNE Der Maaten L V, Hinton G E. Visualizing data using t-S ...

  7. PlatformIO+Jlink进行调试

    PlatformIO自带调试功能具体配置如下 https://docs.platformio.org/en/latest/plus/debug-tools/jlink.html     我是用的是直接 ...

  8. CS5265完全替代兼容龙迅LT8711|Type-C/DP1.2 to HDMI2.0方案芯片|CS5265兼容TYPEC手机笔电

    龙迅LT8711是一款Type-C/DP1.2 to HDMI2.0方案芯片.LT8711HE是一款高性能Type-C/DP1.2至HDMI2.0转换器,设计用于将USB typec或DP1.2源连接 ...

  9. 初遇NFT-IPFS

    初遇NFT-IPFS 本次学习如何使用Hardhat框架制作可预售的NFT并利用IPFS存储元数据. NFT简介 NFT全称Non-fungible Token(即非同质化通证).不可分割性(目前有碎 ...

  10. Pycharm的界面修改与基本设置

    Pycharm的一些基本设置 设置字体大小与字体样式 首先我们打开Pycharm,点击左上角File,找到sitting 点击后就能进入设置界面 xdm,我将介绍两种更改字体的设置 方法一 设置界面找 ...