用Python绑定调用C/C++/Rust库

news2025/3/13 14:23:06

用Python绑定调用C/C++/Rust库

在《让你的Python程序像C语言一样快》我们学习了如何利用Python API来用C语言编写Python模块,通过将核心功能或性能敏感运算用C语言实现,Python程序可以运行地像C语言一样快。然而,很多时候我们需要的功能已经有人实现了,我们并不需要从头再实现一遍,只需要调用封装好的库即可,此时就需要用到Python绑定。Python绑定可以让Python程序调用C/C++/Rust编译的库函数,从而让我们在不重复造轮子的前提下,兼具Python和C/C++二者的优点。

在这里插入图片描述

文章目录

    • Python绑定概述
      • 数据类型转换
      • 理解可变性和不可变性
      • 内存管理
      • 开发环境
      • invoke
    • ctypes
      • 安装
      • 调用函数
      • 加载库
      • 调用函数
      • 优缺点
    • Cython
      • 安装
      • 调用函数
        • 编写绑定
        • 编译绑定
      • 调用函数
      • 优缺点
    • 总结

Python绑定概述

Python绑定适用于如下场景和用例:

  • 已有C/C++/Rust编写的库,为了让库调用更加方便,使其具有Python语言的优势,于是通过Python绑定提供Python调用接口
  • 加速Python代码,将关键代码或性能敏感操作转换为C/C++/Rust实现,从而提升程序性能
  • 使用Python测试工具做大规模测试

数据类型转换

如果你学习过《让你的Python程序像C语言一样快》或者你曾经用C语言写过Python模块,那么你应该体会到,Python调用C需要做的主要工作就是数据的封装和转换。这是因为Python和C的数据存储方式不同——C语言的数据存储更加紧凑。例如,C语言中的uint8_t在内存中只占8位;而Python一切皆对象,即便是最简单的整数,在Python中也要用更多的字节来存储。Python数据在内存中的存储结构跟操作系统、Python版本等诸多因素有关。因此,要想Python与C互相通信,必须对数据进行封装和转换。

我们看一下常见的数据类型

整数: Python可以存储任意精度的整数,在Python中我们可以存储任意大小的整数,无需担心溢出问题。而C语言是有存储上下限的,因此从Python传递整数到C需要格外留意数据大小,谨防C语言整型溢出。

浮点数: Python比C的精度更高,也就是说Python可以存储更大/更小的浮点数,这意味着Python传递浮点数给C需要留意失精度问题。

复数: Python内置复数类型,而C语言在ISO 99中引入了复数类型,它是通过complex.h头文件定义的。C语言的复数其实就是一个数组,数组中有两个元素,一个表示复数的实部,一个表示复数的虚部。因此没有内置的方法可以直接转换Python的复数和C的复数,一般需要定义结构体或类来完成复数的传递和转换。

字符串: 在Python绑定中传递字符串是件棘手的事情,因为Python和C以完全不同的格式存储字符串(与其他数据类型不同,C和C++对字符串的存储也不同)。因此如何处理字符串将是我们后面的重点。

布尔值: 布尔值由于非常简单,因此在转换和传递也都十分简单。

理解可变性和不可变性

除了上面这些基本数据类型外,我们还要了解Python对象是可变的还是不可变的。C函数传参有类似的概念,即传值传址。在C语言中,所有参数都是传值的。如果希望函数可以修改调用者的变量,则需要传递指向该变量的指针。

您可能会想,是否可以通过使用指针将不可变对象传递给C来绕过不可变限制。正常情况下Python不会给你一个指向对象的指针,所以上面的方法是行不通的。(除非你深入到解释器的底层,用暴力的方式暴露对象指针,这样做不但代码丑陋,还会丧失代码的可移植性和安全性。)如果想用C语言修改Python对象,那么需要采取额外的步骤来实现这一点。后面会详细介绍如何实现。

因此,在创建Python绑定时,不变性是需要重点关注的。归根到底我们要处理的是Python与C语言内存管理方式的不同。

内存管理

C语言和Python管理内存的方式非常不同。在C语言中,开发人员需要自己手动管理所有内存的分配与释放,要确保不用的内存及时释放且还不能多次释放。而Python采用垃圾收集器自动管理内存。两种内存管理方式各有千秋,但客观上确实为创建Python绑定增添了额外的麻烦。我们需要知道每个对象的内存分配位置,并确保它只在同一语言环境下释放。

例如,在Python中,当执行x = 3时,会创建一个对象并且由Python的垃圾收集器来管理,对应C语言的代码为:

int* iPtr = (int*)malloc(sizeof(int));

上面的代码在堆上开辟了一块整型大小的空间,并将该空间的地址赋给了iPtr,这里的指针在C语言中需要手动释放。

开发环境

开发Python绑定对开发环境有一定要求,由于需要与外部C/C++/Rust库通信,因此需要一些额外的工具,总体上需要如下部分:

  • Python ≥ 3.6
  • Python开发者工具
    • Linux安装python3-devpython3-devel(取决于所使用的Linux发行版)
    • Windows请参考这里
  • invoke 工具
  • 要绑定的C/C++/Rust库
  • 虚拟环境(可选,推荐)

这里唯一一个没见过的东西就是invoke,下面会单独介绍invoke的安装和使用。

invoke

invoke用于构建和测试Python绑定,它跟make非常相似,只不过用Python脚本替代了Makefiles。

安装invoke非常简单,用pip即可安装

$ pip install invoke

安装完成后,在控制台输入invoke加要执行的任务即可运行指定任务

$ invoke build-cmult
==================================================
= Building C Library
* Complete

我们可以通过--list选项查看当前都支持哪些任务

$ invoke --list
Available tasks:

  all              Build and run all tests
  build-cffi       Build the CFFI Python bindings
  build-cmult      Build the shared library for the sample C code
  build-cppmult    Build the shared library for the sample C++ code
  build-cython     Build the cython extension module
  build-pybind11   Build the pybind11 wrapper library
  clean            Remove any built objects
  test-cffi        Run the script to test CFFI
  test-ctypes      Run the script to test ctypes
  test-cython      Run the script to test Cython
  test-pybind11    Run the script to test PyBind11

从上面的输出可以看到,对于每一个绑定,都有一个以build-开头的构建任务和一个以test-开头的测试任务。此外还有两个特别任务:

  • invoke all 运行所有的构建和测试任务
  • invoke clean 清楚所有生成的文件

了解了invoke的基本用法后,我们就可以着手开始构建我们的Python绑定了。

ctypes

我们首先学习用ctypes构建Python绑定。ctypes是Python的标准库,它提供了一组底层工具集用于加载共享库,并在Python和C之间编排数据。

安装

ctypes最大的优势是它是标准库的一部分,自Python 2.5开始ctypes随Python一起发行,不需要额外安装。使用时直接import进来即可。

调用函数

加载C库并调用函数的所有代码都在Python程序中,这个过程中没有额外的步骤。要在ctypes中创建Python绑定,需要如下步骤:

  1. 加载库
  2. 封装输入参数
  3. 告诉ctypes函数返回类型

加载库

ctypes提供多种方法加载共享库,其中有些是平台特有的。例如,我们可以通过传入所需共享库的完整路径来直接创建一个ctypes.CDLL对象。

# ctypes_test.py
import ctypes
import pathlib

if __name__ == "__main__":
    # 加载共享库
    libname = pathlib.Path().absolute() / "libcmult.so"
    c_lib = ctypes.CDLL(libname)

当共享库与Python脚本位于同一目录中时,这将起作用,但当您尝试加载来自Python绑定以外的包的库时,请小心。

调用函数

共享库的函数定义如下:

// cmult.h
float cmult(int int_param, float float_param);

我们需要传递一个整型一个浮点型,并返回浮点型。整形和浮点型在Python和C中都是原生支持的。

一旦将库加载到Python绑定中,库中函数会成为c_lib的属性,c_lib是我们上一步创建的CDLL对象。我们可以这样调用库中函数:

x, y = 6, 2.3
answer = c_lib.cmult(x, y)

上面的代码看似合理,但执行会有报错:

$ invoke test-ctypes
Traceback (most recent call last):
  File "ctypes_test.py", line 16, in <module>
    answer = c_lib.cmult(x, y)
ctypes.ArgumentError: argument 2: <class 'TypeError'>: Don't know how to convert parameter 2

从报错信息可以看出我们需要告诉ctypes哪些非整型的参数,如果我们不明确告诉ctypes,否则ctypes不知道函数的这些信息。任何未标记的参数都假定为整数。ctyps不知道如何将存储在y中的值2.3转换为整数,因此它失败了。

要解决这个问题,我们需要将数字明确声明成一个c_float。我们可以在调用函数时执行此操作:

# ctypes_test.py
answer = c_lib.cmult(x, ctypes.c_float(y))
print(f"In Python: int: {x} float {y:.1f} return val {answer:.1f}")

现在再运行,将返回传入的两个数字的乘积:

$ invoke test-ctypes
    In cmult : int: 6 float 2.3 returning  13.8
    In Python: int: 6 float 2.3 return val 48.0

等等,Python代码中 6 × 2.3 = 48.0 6 \times 2.3=48.0 6×2.3=48.0,这显然是错的!

造成这个问题的原因与输入参数非常类似,ctypes假设函数返回int。实际上,函数返回了一个浮点值,但它的编排转换方式不正确。就像输入参数一样,您需要告诉ctypes使用不同的类型。这里的语法稍稍不同:

# ctypes_test.py
c_lib.cmult.restype = ctypes.c_float
answer = c_lib.cmult(x, ctypes.c_float(y))
print(f"In Python: int: {x} float {y:.1f} return val {answer:.1f}")

修改后代码就运行正常了:

$ invoke test-ctypes
==================================================
= Building C Library
* Complete
==================================================
= Testing ctypes Module
    In cmult : int: 6 float 2.3 returning  13.8
    In Python: int: 6 float 2.3 return val 48.0

    In cmult : int: 6 float 2.3 returning  13.8
    In Python: int: 6 float 2.3 return val 13.8

优缺点

ctypes最大的优势是它内置在Python标准库中。我们不需要额外安装它,所有功能都内置在Python中。但是ctypes缺乏自动化,开发稍微复杂的项目时会变得非常麻烦。

Cython

Cython用类似Python的语法来创建Python绑定,然后生成可以编译到模块中的C或C++代码。Cython中有多种方法可以创建Python绑定,其中最常用的方法是使用distutilssetup

安装

Cython可以通过pip直接安装:

$ pip install cython

调用函数

要效用共享库函数,我们需要编写绑定,编译绑定,最后在Python代码中调用绑定。Cython支持C和C++。

编写绑定

在Cython中声明模块的最常见形式是使用.pyx文件:

# cython_example.pyx
""" Example cython interface definition """

cdef extern from "cppmult.hpp":
    float cppmult(int int_param, float float_param)

def pymult(int_param, float_param ):
    return cppmult(int_param, float_param )

上面的代码分为2部分:

  • 4-5行告诉Cython我们会调用cppmult.hpp中的cppmult函数
  • 7-8行将cppmult函数封装成Python函数

Cython的语法是C、C++和Python的特殊组合。不过,Python开发者对它会很熟悉,因为Cython主体吸收了Python语法。

第一部分cdef extern告诉Cython下面定义的函数也出现在cppmult.hpp文件中。这样确保Python绑定的函数接口跟C/C++的声明一致。第二部分看上去跟正常的Python函数一样(实际上它就是一个常规的Python函数),这部分创建一个可以访问C++函数cppmult的Python函数。

编译绑定

要编译绑定,首先在.pyx文件上运行Cython以生成.cpp文件。完成后,使用g++对.cpp文件进行编译:

# tasks.py
def compile_python_module(cpp_name, extension_name):
    invoke.run(
        "g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC "
        "`python3 -m pybind11 --includes` "
        "-I /usr/include/python3.7 -I .  "
        "{0} "
        "-o {1}`python3.7-config --extension-suffix` "
        "-L. -lcppmult -Wl,-rpath,.".format(cpp_name, extension_name)
    )

def build_cython(c):
    """ 编译cython扩展模块 """
    print_banner("Building Cython Module")
    # 在.pyx文件上运行Cython以生成.cpp文件
    invoke.run("cython --cplus -3 cython_example.pyx -o cython_wrapper.cpp")

    # 编译并连接cython封装库
    compile_python_module("cython_wrapper.cpp", "cython_example")
    print("* Complete")

上面的代码首先在.pyx文件上运行cython。这里用了几个选项:

  • –cplus告诉Cython生成C++代码
  • -3告诉Cython生成Python3代码
  • -o cython_wrapper.cpp指定生成文件名称

生成C++文件后,就可以使用C++编译器生成Python绑定。用invoke运行上面定义的任务:

$ invoke build-cython
==================================================
= Building C++ Library
* Complete
==================================================
= Building Cython Module
* Complete

从输出可以看到,运行上面的任务会先构建cppmult库,然后再构建cython模块封装该库。完成构建后我们就得到Cython版Python绑定了。

调用函数

在Python中调用Python绑定函数跟调用常规Python函数一样:

# cython_test.py
import cython_example

x, y = 6, 2.3

answer = cython_example.pymult(x, y)
print(f"In Python: int: {x} float {y:.1f} return val {answer:.1f}")

这里第二行引入的就是新构建的Python绑定模块,第7行调用模块中的pymult()方法。pymult()是.pyx文件提供的cppmult()的Python封装,并将其重命名为pymult()。我们用invoke运行测试:

$ invoke test-cython
==================================================
= Testing Cython Module
    In cppmul: int: 6 float 2.3 returning  13.8
    In Python: int: 6 float 2.3 return val 13.8

优缺点

Cython是一个相对复杂的工具,它可以在用C或C++创建Python绑定时为提供深层次的控制。虽然这里没有深入介绍,但它提供了一种Python风格的方法来编写手动控制GIL的代码,这可以显著加速某些问题。

然而,尽管Cython像Python但并不完全是Python,因此,当你使用Cython时,会有一个轻微的学习曲线。

总结

Python中创建Python绑定的工具还有很多,上面只是给大家介绍了最具代表性的2个工具:ctypes是Python自带的,Cython可以用类似Python语法的cython语言构建Python绑定。除了这2个工具外,还有CFFIPyBind11PyBindGenBoost.PythonSIPcppyyShibokenSWIG等,这些攻击原理大同小异,大家可以自行去学习了解。

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

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

相关文章

Mycat(11):分片详解之字符串ID处理

1 找到conf/schema.xml并备份 2 字符串ID处理的分区算法 conf/rule.xml <tableRule name"jch"><rule><columns>id</columns><algorithm>jump-consistent-hash</algorithm></rule></tableRule><function name&qu…

css之grid布局个人学习笔记

前置 只是个人学习,内容只会记录自己想知道,有问题的知识点具体可以看看bilibili的耕耕技术宅-grid布局地址视频对应的耕耕技术宅-grid布局ppt地址学有余力的可以通关下这个小游戏通过给萝卜浇水&#xff0c;学习 CSS 网格布局CSS Grid 网格布局教程- 阮一峰 明确基本概念 下…

品牌创建百科怎么写?品牌百度百科怎么创建?

每年都会有很多新成立的品牌&#xff0c;但是能够被大众所熟知的却是寥寥无几&#xff0c;众多品牌都被淹没在了大千世界里。 一个品牌的创建&#xff0c;难的就在于宣传&#xff0c;宣传力度不到位&#xff0c;就没有用户会愿意为这个品牌买单。 那么怎么快速提升品牌的知名度…

肝完这在“牛客网”难倒万人的Java面试题后,已收获9个大厂offer

上周在牛客网看到了这几百道面试题之后&#xff0c;看到评论区全是太难了&#xff01;太难了&#xff0c;就深深被其吸引&#xff0c;索性直接花了一周的时间才把它们全部解析出来做成了这份文档&#xff0c;发给了最近面试的粉丝&#xff0c;他刷爆之后居然能拿到了好几个大厂…

IDEA集成docker-JDK11版本

IDEA集成docker 1. docker 服务器开启远程访问 登录 docker 所在的远程服务器&#xff0c;使用命令 vim /usr/lib/systemd/system/docker.service 修改配置文件&#xff0c;需注意&#xff0c;修改时确认自己的账户拥有相应权限 主要操作是找到 ExecStart/usr/bin/dockerd -H…

能量原理和变分法笔记1:变分法简介

上个学期在学校学了多体系统动力学的课&#xff0c;其中老师讲了变分原理&#xff0c;觉得很有启发&#xff0c;决定再学学相关的知识&#xff0c;在B站找到了一个这样的视频能量原理与变分法&#xff0c;做点笔记&#xff0c;加深一下理解。 第0章序言-微元、功和能(P2)第1章1…

常用的wxpython控件使用方法总结

写在开头&#xff1a;总结下现阶段我常用到wxpython控件的一些使用方法&#xff0c;便于记录和查询。 我一般是借助wxFormBuilder工具搭建基础的界面生成代码&#xff0c;这样做的好处自然是方便设计界面增加界面的美观度&#xff0c;再在.py文件手写代码设置控件的事件驱动&a…

【论文阅读总结】Batch Normalization总结

批量规范化&#xff1a;通过减少内部协变量转移加快深度网络训练1. 摘要2. 序言2.1 min-batches的优缺点2.2 批量归一化解决内部协变量转移的优点3.减少内部协变量转移实现思想3.1 白化的问题3.2 解决白化问题4.小批量统计进行标准化4.1.白化简化的两种方式4.1.1 对通道维度进行…

mybatis实现分页查询(两种方式:1pageHelper插件 2手写)

方法1&#xff1a;整合pageHelper分页插件 优点&#xff1a;快捷&#xff0c;只需要你有一个查询全部数据的方法即可 缺点&#xff1a;对于初学者来说&#xff0c;不了解内部的原理 前提&#xff1a;需要先实现一个最简单的 查询全部数据的方法&#xff0c;不会的可以先去搭建一…

C++零基础项目:俄罗斯方块!详细思路+源码分享

游戏介绍 这是使用 C 和 EasyX 写的一个俄罗斯方块小游戏&#xff0c;里面用到的 C 特性并不多。 游戏主要分成了两个类来实现&#xff1a;Game 和 Block 类&#xff0c;分别用来实现游戏逻辑和单独的俄罗斯方块&#xff0c;里面顶多就用到了静态成员函数和变量的特性&#x…

nexus上传自定义starter

nexus上传自定义starter1、starter上传简介2、上传方法2.1、setting.xml文件2.2、项目中的pom文件3、具体部署1、starter上传简介 在我们自定义了springboot的starter后&#xff0c;starter一般有是一个父子级maven工程&#xff0c;如下图所示&#xff0c;对于 autoconfigure 来…

H5 导航栏示例

本文是通过:hover更新元素样式&#xff0c;通过递归树形菜单渲染到页面。 效果 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"&…

基于ssm框架的汽车故障维修管理系统源码+开题报告+论文+远程安装部署+视频讲解

博主介绍&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于ssm框架的汽车故障维修管理系统源码开题报告论文远程安装部署视频讲解 演示视频 视频去哪了呢&#xff1f;_哔哩哔哩_bilibili 系统介绍 项目介绍…

dependencies与dependencyManagement的区别

最近再看项目的时候&#xff0c;无意间注意到项目中的pom文件既有dependencyManagement&#xff0c;也有dependencies&#xff0c;有点疑惑为什么要同时有这两个标签&#xff0c;可能之前没太注意过吧。 dependencies与dependencyManagement的区别&#xff1a; a…

js基础面试题

js基础 h5新特性 1. 新增选择器&#xff1a;querySelector、querySelectorAll 2. 拖拽功能&#xff1a;drag和drop 3. 媒体播放&#xff1a;video和audio 4. 本地存储&#xff1a;localStorage和sessionStorage 5. 语义化标签&#xff1a;article、footer、header、nav、sect…

D. Maximum Sum of Products(二维数组记录改变区间)

Problem - 1519D - Codeforces 给你两个长度为n的整数数组a和b。 你最多可以扭转数组a的一个子数组&#xff08;连续子段&#xff09;。 你的任务是反转这样一个子数组&#xff0c;使其总和∑i1nai⋅bi达到最大。 输入 第一行包含一个整数n&#xff08;1≤n≤5000&#xff0…

第三十四章 linux-模块的加载过程四

第三十四章 linux-模块的加载过程四 文章目录第三十四章 linux-模块的加载过程四调用模块的初始化函数释放INT section所占用的空间呼叫模块通知链模块的卸载find_module检查依赖关系free_modulesys_init_module第二部分由load_module返回的do_init_module实现 static noinlin…

LCHub:到2023年,全球低代码市场预计达到269亿美元

12月13日,Gartner发布全球低代码市场规模报告。数据显示,到2023年,全球低代码市场规模预计达到269亿美元,同比增长19.6%。 业务技术专家认为,到2026年,超级自动化和业务可组合性将成为加速低代码技术应用的关键驱动力。 Gartner还发布了一项调查数据,到2023年全球超级自…

计算机毕设Python+Vue学习类视频网站(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

C++ STL 之堆栈(后进先出) stack 详解

文章目录Part.I AttentionPart.II FuncitonPart.III CodePart.I Attention stack<T>容器适配器的数据是以 LIFO (Last in First Out, 后进先出) 的方式组织的&#xff0c;可以将它想象成放在餐桌上的一摞盘子。必须要包含头文件#include <stack> Part.II Funcito…