项目使用PyO3最新版本:0.22.2
新版的PyO3除了一些用法的改变,在新版本中引入了 Bound 的概念:
Bound 是一个新的智能指针类型,用于表示与特定 Python 解释器生命周期绑定的 Python 对象。这个改变的主要目的是提高类型安全性和明确所有权。
导入方式的变化:
旧版:py.import("numpy")
新版:py.import_bound("numpy")
新版本返回一个 Bound 对象,这更明确地表示了导入的模块与当前 Python 解释器实例的关系。
函数签名的变化:
旧版:pub fn run_config<'a>(config: &'a PyDict) -> PyResult<&'a PyDict>
新版:pub fn run_config<'a>(config: &Bound<'a, PyDict>) -> PyResult<Bound<'a, PyDict>>
这个变化使得函数签名更明确地表达了 Python 对象的生命周期和所有权。
优势:
更好的类型安全:Bound 可以在编译时捕获更多的潜在错误。
明确的生命周期:使得 Rust 的借用检查器能更好地理解 Python 对象的生命周期。
更好的所有权模型:让 Rust 代码更容易推理 Python 对象的所有权和生命周期。
- 使用pip打包Rust代码
- 使用PyO3 crate创建Rust接口
- 为Rust包构建测试
- 将复杂的python对象传递到Rust中
- 检查和使用自定义python对象
- 在Rust中构建自定义python对象
- 在NumPy中构建模型
- 在Rust中使用NumPy和其他Python模块
- 在Rust中重建NumPy模型
- 将端到端(End-to-End)解决方案构建为一个包
- 使用和测试包
项目在另一个仓库中
https://github.com/lijiachang/flask_with_rust
Choose one of the three ways:
pip install git+https://github.com/lijiachang/python_rust@master
pip3 install setuptools-rust
python setup.py build && python setup.py install
python setup.py build
这个命令编译项目中的任何 C 扩展(如您的 Rust 扩展)。
它不会安装任何东西,只是准备项目以供安装。
python setup.py install
这个命令将项目安装到 Python 的 site-packages 目录中。
它会复制所有必要的文件到安装目录,包括编译好的扩展。
这是一个"静态"安装 - 如果您更改了源代码,需要重新运行此命令来更新安装。
python setup.py develop
这个命令创建一个到您的项目目录的链接,而不是复制文件。
它仍然会编译和安装任何 C 扩展。
主要用于开发过程中,因为它允许您修改源代码而不需要重新安装。
对源代码的任何更改都会立即反映在已安装的包中。
这种安装方式也被称为"可编辑"或"开发"模式。
构建 Rust 扩展:
cargo build --release
这会在 target/release 目录下生成一个动态库文件(.so、.dll 或 .dylib)
在 Windows 上是 .dll,在 macOS 上是 .dylib,在 Linux 上是 .so
然后,运行以下命令来安装 Python 包:
pip install .
这个命令会调用 setup.py,编译 Rust 代码,并安装 Python 包。
如果安装成功,您应该能够在 Python 中导入和使用这个模块了。
hello
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example import say_hello
>>>
>>> say_hello()
saying hello from Rust~
saying hello from Rust~
fibonacci_number
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example import fibonacci_number
>>> from pyo3_example import fibonacci_numbers
>>> fibonacci_number(20)
6765
>>> fibonacci_numbers([20,21,22])
[6765, 10946, 17711]
>>>
通过setup.py 中定义entry_points -> console_scripts 实现为系统终端添加命令工具
(venv) ➜ python_rust git:(master) ✗ fib-number
usage: fib-number [-h] --number NUMBER
fib-number: error: the following arguments are required: --number
(venv) ➜ python_rust git:(master) ✗ fib-number --number 20
your fibonacci number is:6765
在python控制台中测试适配器接口
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example import FibNumberAdapter
>>> test = FibNumberAdapter(10)
>>> test2 = FibNumberAdapter(15)
>>> test2.count
2
>>> test.count
2
>>> test2.success
True
>>> test2.result
610
>>> test_list = FibNumberAdapter([5,6,7,8,9])
>>> test_list.result
[5, 8, 13, 21, 34]
>>> test.count
3
>>> test2.count
3
>>> test_list.count
3
>>>
将复杂的python对象传递到Rust中,
config_number_command示例,读取yaml文件,转换为dict,传递给Rust处理
(venv) ➜ src git:(master) ✗ pip install pyyaml
...
(venv) ➜ src git:(master) ✗ pwd
/Users/lijiachang/code/rust/python_rust/src
(venv) ➜ src git:(master) ✗ config-fib --path ../example.yml
config data:
{'number': [4, 7, 2], 'numbers': [[12, 15, 20], [15, 19, 18]]}
the result:
{'NUMBER RESULT': [3, 13, 1],
'NUMBERS RESULT': [[144, 610, 6765], [610, 4181, 2584]],
'number': [4, 7, 2],
'numbers': [[12, 15, 20], [15, 19, 18]]}
检查和使用自定义python对象
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example import ObjectInterface
>>> test = ObjectInterface([5,6,7,8], [])
>>> test.process()
>>> test.number_results
[5, 8, 13, 21]
>>>
在Rust中构建自定义python对象
可以看到Rust对象可在python系统中无缝的工作
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example import FibProcessor
>>> test = FibProcessor([11,12,13,14], [[11,12], [13,14], [15,16]])
>>> test.numbers_results
[[89, 144], [233, 377], [610, 987]]
>>>
>>> test.numbers_results = "write" # 报错是因为只读
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'numbers_results' of 'builtins.FibProcessor' objects is not writable
>>>
>>> test.number = [1,2] # 可读写
>>> test.number
[1, 2]
>>>
在Rust中使用NumPy模块
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example import test_numpy
>>> outcome = test_numpy({})
>>> outcome["numpy result"].transpose()
array([[70, 90]])
>>>
在Rust中重建NumPy模型
(venv) ➜ src git:(master) ✗ python
Python 3.10.11 (v3.10.11:7d4cc5aa85, Apr 4 2023, 19:05:19) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from pyo3_example.numpy_interface import NumpyInterface
>>> test = NumpyInterface()
>>> test.calc_times(10, 20)
>>> test.calc_parameters(70, 90)
>>>
>>> test.inventory
{'car time': np.int64(70), 'trunk time': np.int64(90), 'distance': np.float64(9.999999999999998), 'traffic grade': np.float64(20.0)}
>>>
将端到端(End-to-End)解决方案构建为一个包
在Rust中建立一个(灾难)模型,然后在Python中使用它
>>> from oasis_risk_modelling import construct_model
>>> construct_model([1,2])
/Users/lijiachang/code/rust/python_rust/venv/lib/python3.10/site-packages/oasis_risk_modelling
[{'vulnerability_id': 1, 'intensity_bin_id': 1, 'damage_bin_id': 1, 'damage_probability': 0.44999998807907104, 'event_id': 1, 'areaperil_id': 10, 'footprint_probability': 0.4699999988079071, 'total_probability': 0.21149998903274536}, {'vulnerability_id': 1, 'intensity_bin_id': 1, 'damage_bin_id': 1, 'damage_probability': 0.44999998807907104, 'event_id': 2, 'areaperil_id': 20, 'footprint_probability': 0.30000001192092896, 'total_probability': 0.13500000536441803}, {'vulnerability_id': 1, 'intensity_bin_id': 2, 'damage_bin_id': 2, 'damage_probability': 0.6499999761581421, 'event_id': 1, 'areaperil_id': 10, 'footprint_probability': 0.5299999713897705, 'total_probability': 0.34449997544288635}, {'vulnerability_id': 1, 'intensity_bin_id': 2, 'damage_bin_id': 2, 'damage_probability': 0.6499999761581421, 'event_id': 2, 'areaperil_id': 20, 'footprint_probability': 0.699999988079071, 'total_probability': 0.45499998331069946}, {'vulnerability_id': 3, 'intensity_bin_id': 1, 'damage_bin_id': 1, 'damage_probability': 0.8899999856948853, 'event_id': 1, 'areaperil_id': 10, 'footprint_probability': 0.4699999988079071, 'total_probability': 0.41830000281333923}, {'vulnerability_id': 3, 'intensity_bin_id': 1, 'damage_bin_id': 1, 'damage_probability': 0.8899999856948853, 'event_id': 2, 'areaperil_id': 20, 'footprint_probability': 0.30000001192092896, 'total_probability': 0.2670000195503235}, {'vulnerability_id': 4, 'intensity_bin_id': 2, 'damage_bin_id': 1, 'damage_probability': 0.3499999940395355, 'event_id': 1, 'areaperil_id': 10, 'footprint_probability': 0.5299999713897705, 'total_probability': 0.18549998104572296}, {'vulnerability_id': 4, 'intensity_bin_id': 2, 'damage_bin_id': 1, 'damage_probability': 0.3499999940395355, 'event_id': 2, 'areaperil_id': 20, 'footprint_probability': 0.699999988079071, 'total_probability': 0.2449999898672104}]
>>>
使用和测试包
- 使用pandas重新构建Python版本的构造模型
- 随机时间ID生成器函数
- 用一系列不同的数据来对比Python和Rust的计时测试
cd oasis_risk_modelling
python test_model_python_vs_rust.py
可以看到,刚开始的时候Rust实现比Python pandas会更快。但是后面会比pandas慢。
因为我们写的rust代码不能很好的拓展,只是在循环中执行循环。而在pandas中,则总是将总概率向量化。
不要在项目根目录(setup.py、Cargo.toml所在目录)执行python终端,因为pyo3_example目录会冲突