分享

[Rust 开发]PyO3:Rust与Python的联动编程(上)

 godxiasad 2023-04-21 发布于北京

前言

Rust语言经常被人误认为是R语言,或者Ruby语言……但是做为近十年来tiobe最出人意料的编程语言,从冷门逐渐变成了明星,不过这次我们不讲Rust入门的内容,我们先来看看它一个很实用的功能——与Python的联动编程

在正式开始之前,可以先看以下几个问题:

  • 为什么要用Rust?

  • 答:Rust有很多优点,也有很多缺点,但是下面这个优点可以覆盖所有的缺点:即它能够编写出保证永远不出现内存错误的程序!!

 编程领域最头痛的问题,就是内存错误,这种错误无法排查,无法重现,随机出现,只能靠着程序员的编码能力,在编写代码的时候小心翼翼的检查以排除,所以C/C++虽然号称也可以写出内存安全的程序,但是实际上在大型程序中,几乎是一种可望不可及的理想状态。而Rust以其特有的所有权设计思想,保证了在任何情况下的内存安全,只要编译通过,就永远不会出现内存错误

  • 注意:不出现内存错误,不代表永远不会出现错误,而是出现任何错误,Rust都会告诉为什么会错,不会仅仅弹出一个不知所云的内存问题,让你无从下手,无从排查,也无法解决。


正文

全文一共4节,大约分为三章讲完,内容如下

  • 第一节:PyO3的简介,聊一下Python的联合开发方式,以及什么是PyO3

  • 第二节:从0开始编写一个PyO3程序,并且在Python中进行调用

  • 第三节:介绍PyO3提供的官方的脚手架,让我们如何快速的创建和编译Python扩展项目

  • 第四节:编写一系列计算密集型的插件,并且与Python原生代码和Python的JIT优化模式进行效率对比

PART/
01
第一节: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,实际上指的是Cython,即C语言实现的Python,广义上,Python还有Jython和IronPython这两个版本,分别是Java的Python实现和.Net的Python实现,不过后面这两个东西,已经很久没有更新了,属于死了但是没有完全死的状态,基本上我们可以忽略……
所以,现在我们说的Python,事实上指的就是Cython,所以可以认为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开发出来的扩展模块,有如下特点:

  1. 开发出来的扩展模块,运行于Python同进程中,所以可以直接在Python语言环境中导入、调用、监控和管理。

  2. 可以在Rust中编写的、编译好的可执行程序中,调用和动态执行Python代码的能力,并且提供两种语言之间的控制与数据交互能力。


PART/
02
第二节:Pyo3的第一个程序:say hello

首先还是用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的功能实现代码了,主要需要编写两个部分: 
  1. 编写逻辑业务实现部分,我们这里实现一个say hello的功能,也就是用户输入一个用户名,这里输出一个问候,并且告诉他,这个问候来自Rust编写的后台扩展。

如果需要直接封装成Python可以调用的方法,需要在前面加#[pyfunction]这个属性,表示这个方法将对外暴露,并且被直接封装成Python可以调用的方法。

  1. 可以写一个测试方法,因为Rust是一种编译执行的语言,所以要执行一个方法必须要有main函数,如果不想添加main.rs和main函数的话,可以写一个测试方法,在cargo里面测试用。

  2. 把写好的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仓库如下位置:
https:///godxia/blog/tree/master/002PyO3/pyo3demo
注意: 我的代码里面还有后续的测试的内容,特别包含了polars包,所以编译起来比较慢,也特别大……(如果你直接编译我的源码,可能要十几分钟到几十分钟……)
到这里了,请使用使用量子学习法:
点赞就是看过了
转发就是学会了
收藏就是融会贯通了

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多