Rust 开发系列PyO3:Rust与Python的联动编程(上)
Posted 虾神说D
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rust 开发系列PyO3:Rust与Python的联动编程(上)相关的知识,希望对你有一定的参考价值。
前言
Rust语言经常被人误认为是R语言,或者Ruby语言……但是做为近十年来tiobe最出人意料的编程语言,从冷门逐渐变成了明星,不过这次我们不讲Rust入门的内容,我们先来看看它一个很实用的功能——与Python的联动编程。
在正式开始之前,可以先看以下几个问题:
- 为什么要用Rust?
- 答:Rust有很多优点,也有很多缺点,但是下面这个优点可以覆盖所有的缺点:即它能够编写出保证永远不出现内存错误的程序!!
编程领域最头痛的问题,就是内存错误,这种错误无法排查,无法重现,随机出现,只能靠着程序员的编码能力,在编写代码的时候小心翼翼的检查以排除,所以C/C++虽然号称也可以写出内存安全的程序,但是实际上在大型程序中,几乎是一种可望不可及的理想状态。 而Rust以其特有的所有权设计思想,保证了在任何情况下的内存安全,只要编译通过,就永远不会出现内存错误。
- 注意:不出现内存错误,不代表永远不会出现错误,而是出现任何错误,Rust都会告诉为什么会错,不会仅仅弹出一个不知所云的内存问题,让你无从下手,无从排查,也无法解决。
正文
全文一共4节,大约分为三章讲完,内容如下
- 第一节:PyO3的简介,聊一下Python的联合开发方式,以及什么是PyO3
- 第二节:从0开始编写一个PyO3程序,并且在Python中进行调用
- 第三节:介绍PyO3提供的官方的脚手架,让我们如何快速的创建和编译Python扩展项目
- 第四节:编写一系列计算密集型的插件,并且与Python原生代码和Python的JIT优化模式进行效率对比
第一节:Python扩展开发与PyO3简介
Python的优点,数不胜数,但是如果要说Python的缺点,那么下面这个应该是所有Python程序员最大的痛点:
Python有多慢呢? 下面这个是葡萄牙米尼奥大学和葡萄牙科英布拉大学联合的HAS Lab/INESC TEC(高性能计算实验室)在2020年11月发表的一篇论文,论文中对流行的编程语言进行了基准性能测试:
可以看见,无论是性能、时间、还是内存消耗,Python都几乎是倒数,特别是在计算性能和消耗时间上,几乎只有C、Rust、C++这三者的七十分之一!
当然,这里说个题外话,高性能有若干前提要求,不是说Python本身慢,写出来的东西就一定很慢,Python有大量优化方式和手段,例如静态编译(JIT),或者使用numpy这种高性能包,都能够大大提升Python的性能。
另外,程序运行的模式,也会对代码编写和架构有很大的影响,一般来说,在IO(数据)密集型运算上,管你用什么语言,性能都只能被数据吞吐这块短板所拖累……今天不谈这些内容,今天我们主要就针对计算(CPU)密集型运算这种与编程语言和编译器有密切关系的方式。
所以,抛开一切其他条件不谈,我们的诉求,就是一句话:
速度!
速度!!
还TM是速度!!
如果说Python原生的速度,就像体重和身高一样的正方体肥仔虾一样,从小学开始到大学毕业,体育考试中的长跑就从来没有及格过,基本上等于放弃治疗的话,如何最简单的提速呢?
请圣言
人和动物的区别在于,人能够创造工具,动物可以使用工具,但不能创造工具。
——卡尔 马克思
回过来,肥宅Py如何利用工具,变成狂飙Py呢?这就要从Python的扩展开发模式来看了:
首先,我们经常说的,Python,实际上指的是CPython,即C语言实现的Python,广义上,Python还有Jython和IronPython这两个版本,分别是Java的Python实现和.Net的Python实现,不过后面这两个东西,已经很久没有更新了,属于死了但是没有完全死的状态,基本上我们可以忽略……
所以,现在我们说的Python,事实上指的就是CPython,所以可以认为Python的底层就是C语言,那么官方提供的Python的扩展方式,就是用C语言来编写扩展模块(Python的标准库,绝大部分都是C语言开发的,C++属于蹭C语言的框架)。
其他语言如果扩展Python,只能通过如RPC(远程过程调用)这种方式,或者语言扩展桥接绑定的方式来实现,这两种模式就不能叫做扩展开发了。
那么Rust对于Python的扩展开发,又是哪一种方式呢? 答案是基于C语言的桥接模式。Rust可以直接继承C/C++的所有工具和库,也能够支持把自己编译成与C一样的动态链接库,所以我们可以把PyO3看成是Rust绑定C语言的一种扩展方式,也就是说,Rust PyO3写出来的扩展,与原生C写出来的扩展,对于Python是完全一样的。
PyO3开发出来的扩展模块,有如下特点:
- 开发出来的扩展模块,运行于Python同进程中,所以可以直接在Python语言环境中导入、调用、监控和管理。
- 可以在Rust中编写的、编译好的可执行程序中,调用和动态执行Python代码的能力,并且提供两种语言之间的控制与数据交互能力。
第二节:Python扩展开发与PyO3简介
首先还是用Rust的cargo包管理器去创建一个lib工程,这一步没啥说的。
如果你是一位从来没有接触过Rust的同学,那么可以先不必深究这些细节,建议直接看后面的结果。 然后就采用量子学习法即可:
点赞就是看过了
收藏就是学会了
转发就是融会贯通了
然后在crates.io网站上,找到PyO3包,并且把它添加到配置文件里面去。当然,如果你很熟练了,也不用去网站上找,直接添加也行(建议去网站上,查找最新的版本号。)
crates.io是Rust的一个包管理仓库网站,类似于Python的pypi。
添加配置项,注意,需要有两个必选的配置项:
crate-type = ["cdylib"]
这个表示编译时候使用的c标准的动态库 Python的底层就是用c语言写的,必须是c标准库,Python才能导入
pyo3 = version = "0.18.1", features = ["extension-module"]
给工程添加最新的pyo3版本,并且设置特性为扩展模块,这个版本号一般我都选择最新的,所以在添加前都回去crates.io网站上查阅一下,不过你也可以通过
cargo add pyo3
这个命令来添加
之后,就可以编写Rust的功能实现代码了,主要需要编写两个部分:
- 编写逻辑业务实现部分,我们这里实现一个say hello的功能,也就是用户输入一个用户名,这里输出一个问候,并且告诉他,这个问候来自Rust编写的后台扩展。
如果需要直接封装成Python可以调用的方法,需要在前面加#[pyfunction]这个属性,表示这个方法将对外暴露,并且被直接封装成Python可以调用的方法。
- 可以写一个测试方法,因为Rust是一种编译执行的语言,所以要执行一个方法必须要有main函数,如果不想添加main.rs和main函数的话,可以写一个测试方法,在cargo里面测试用。
- 把写好的Python扩展方法,封装到Python模块声明里面去,写一个Python模块声明方法。
#[pymodule]属性表示这个模块里面,要封装暴露哪些方法出来,而且还声明了是否有输入和输出参数。
全部写完之后,可以通过cargo test测试一下,方法是否能够正常执行,之后就可以打包封装了。
直接输入命令:
cargo build --release
对工程进行编译,在这个编译的过程中,cargo编译器会从网络上拉下一些依赖,然后在本地直接编译好,但是这个过程比较慢……特别是大型工程,一次编译可能需要几十分钟之久。
不过好在我们这个工程中没有什么需要依赖的大型库,所以编译还算快,在我本机上,11秒就编译好了。
之后,在工程的/target/release目录下面,会生成一个叫做pyo3demo.dll的动态链接库文件,这个就是windows平台下面的Python扩展模块,如果在Linux下面,会编程成so文件。
因为我们这里是一个裸奔的版本,所以后面还需要手动改一下名字,在Python中,扩展库的的后缀名都是pyd,所以需要我们手动把pyo3demo.dll改名为pyo3demo.pyd。
之后把这个pyd文件,拷贝到你的Python包索引环境的目录下面去,就可以了,最简单的就是直接拷贝到你py文件同目录下。
然后按照Python开发的一般模式,import这个包,然后直接使用即可:
如果输出的内容是我们测试中预想一样的,那就表示成功了。
最后,给出这个say hello应用中可能出现的一些场景问题:
打完收工。
最后,本工程的全部源码,在我gitee仓库如下位置:
注意: 我的代码里面还有后续的测试的内容,特别包含了polars包,所以编译起来比较慢,也特别大……
以上是关于Rust 开发系列PyO3:Rust与Python的联动编程(上)的主要内容,如果未能解决你的问题,请参考以下文章
Rust 开发系列PyO3:Rust与Python的联动编程(下)