- 安装 PyO3
- rust 调用 python
安装 PyO3
-
安装 python
env PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.10-dev
-
创建并进入虚拟环境
pyenv virtualenv 3.10-dev pyo3 pyenv activate pyo3
-
指定动态库
export LD_LIBRARY_PATH=/home/wilson/.pyenv/versions/3.10-dev/lib/:/lib:/usr/lib:/usr/local/lib
-
创建 rust 工程
cargo new demo
-
设置
Cargo.toml
[dependencies.pyo3] version = "0.17.3" features = ["auto-initialize"]
-
main.rs
use pyo3::prelude::*; use pyo3::types::IntoPyDict; fn main() -> PyResult<()> { Python::with_gil(|py| { let sys = py.import("sys")?; let version: String = sys.getattr("version")?.extract()?; let locals = [("os", py.import("os")?)].into_py_dict(py); let code = "os.getenv('USER') or os.getenv('USERNAME') or 'Unknown'"; let user: String = py.eval(code, None, Some(&locals))?.extract()?; println!("Hello {}, I'm Python {}", user, version); Ok(()) }) }
-
-
运行
cargo run
rust 调用 python
调用 python 函数
- PyO3提供了多个api来进行函数调用
- 接受args和kwargs参数的API
- call 调用任何可调用的Python对象
- call_method 调用Python对象上的方法
- 简单调用的API
- call1 和 call_method1 调用只有位置参数的函数
- call0 和 call_method0 调用无参函数
- 为了方便起见,
Py<T>
智能指针公开了这六个API方法,但需要一个Python令牌作为额外的第一个参数来证明GIL是存在的
- 接受args和kwargs参数的API
-
调用 python 函数
use pyo3::prelude::*; use pyo3::types::PyTuple; fn main() -> PyResult<()> { let arg1 = "arg1"; let arg2 = "arg2"; let arg3 = "arg3"; Python::with_gil(|py| { let fun: Py<PyAny> = PyModule::from_code( py, "def example(*args, **kwargs): if args != (): print('调用位置参数', args) if kwargs != {}: print('调用字典参数', kwargs) if args == () and kwargs == {}: print('调用无参函数')", "", "", )? .getattr("example")? .into(); fun.call0(py)?; let args = PyTuple::new(py, &[arg1, arg2, arg3]); fun.call1(py, args)?; let args = (arg1, arg2, arg3); fun.call1(py, args)?; Ok(()) }) }
-
执行结果
$ cargo run 调用无参函数 调用位置参数 ('arg1', 'arg2', 'arg3') 调用位置参数 ('arg1', 'arg2', 'arg3')
-
- 创建关键字参数
- 关键字参数可以是None或
Some(&PyDict)
- 可以通过
IntoPyDict
特性将其他类字典容器(HashMap
,BTreeMap
)转换为字典 - 可以转换最多 10 个元素的元组和
Vec
,每个元素是两个elem的元组
use pyo3::prelude::*; use pyo3::types::IntoPyDict; use std::collections::HashMap; fn main() -> PyResult<()> { let key1 = "key1"; let val1 = 1; let key2 = "key2"; let val2 = 2; Python::with_gil(|py| { let fun: Py<PyAny> = PyModule::from_code( py, "def example(*args, **kwargs): if args != (): print('调用位置参数', args) if kwargs != {}: print('调用字典参数', kwargs) if args == () and kwargs == {}: print('调用无参函数')", "", "", )? .getattr("example")? .into(); fun.call0(py)?; let kwargs = [(key1, val1)].into_py_dict(py); fun.call(py, (), Some(kwargs))?; let kwargs = vec![(key1, val1), (key2, val2)]; fun.call(py, (), Some(kwargs.into_py_dict(py)))?; let kwargs = HashMap::from([(key1, 1)]); fun.call(py, (), Some(kwargs.into_py_dict(py)))?; Ok(()) }) }
-
执行结果
$ cargo run 调用无参函数 调用字典参数 {'key1': 1} 调用字典参数 {'key1': 1, 'key2': 2} 调用字典参数 {'key1': 1}
- 关键字参数可以是None或
访问 python API
- 通过
PyModule::import
获取Python模块的句柄 - 它可以导入和使用环境中可用的任何Python模块
-
调用内建模块
use pyo3::prelude::*; fn main() -> PyResult<()> { Python::with_gil(|py| { let builtins = PyModule::import(py, "builtins")?; let total: i32 = builtins .getattr("sum")? .call1((vec![1, 2, 3],))? .extract()?; println!("total: {total}"); Ok(()) }) }
-
运行一个表达式,可以使用
eval
use pyo3::prelude::*; fn main() -> PyResult<()> { Python::with_gil(|py| { let result = py .eval("[i * 10 for i in range(5)]", None, None) .map_err(|e| { e.print_and_set_sys_last_vars(py); }) .unwrap(); let res: Vec<i64> = result.extract().unwrap(); println!("result: {res:?}"); Ok(()) }) }
执行结果
result: [0, 10, 20, 30, 40]
- 执行多个 python 语句
- [Python::run](https://docs.rs/pyo3/0.17.3/pyo3/struct.Python.html#method.run是执行一个或多个 Python 语句的方法
- 这个方法不返回任何内容(就像任何 Python 语句一样) ,但是您可以通过区域变量 dict 访问受操作的对象。
- 你也可以使用 py_run!宏,这是
Python::run
的缩写。由于 py_run!异常时会panic
,我们建议您只使用此宏快速测试您的Python扩展
use pyo3::prelude::*; use pyo3::{py_run, PyCell}; #[pyclass] struct UserData { id: u32, name: String, } #[pymethods] impl UserData { fn as_tuple(&self) -> (u32, String) { (self.id, self.name.clone()) } fn __repr__(&self) -> PyResult<String> { Ok(format!("User {}(id: {})", self.name, self.id)) } } fn main() -> PyResult<()> { Python::with_gil(|py| { let userdata = UserData { id: 34, name: "wilson".to_string(), }; let userdata = PyCell::new(py, userdata).unwrap(); let userdata_as_tuple = (34, "wilson"); py_run!(py, userdata userdata_as_tuple, r#" print((userdata)) print(userdata.as_tuple()) "#); Ok(()) }) }
执行结果
User wilson(id: 34) (34, 'wilson')
-
使用 pytho 的 context manager
-
通过直接调用
__enter__
和__exit__
使用context 管理器use pyo3::prelude::*; use pyo3::types::PyModule; fn main() { Python::with_gil(|py| { let custom_manager = PyModule::from_code( py, r#" class House(object): def __init__(self, address): self.address = address def __enter__(self): print(f"欢迎来到 [{self.address}]!") def __exit__(self, type, value, traceback): if type: print(f"对不请,你的 [{self.address}] 有问题") else: print(f"感谢您参观 [{self.address}],下次再见") "#, "house.py", "house", ) .unwrap(); let house_class = custom_manager.getattr("House").unwrap(); let house = house_class.call1(("123 大街",)).unwrap(); house.call_method0("__enter__").unwrap(); let result = py.eval("undefined_variable + 1", None, None); // 如果 eval 抛出异常,它会被 context manager 抓住 // 否则,__exit__ 的参数是 None match result { Ok(_) => { let none = py.None(); house .call_method1("__exit__", (&none, &none, &none)) .unwrap(); } Err(e) => { house .call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback(py))) .unwrap(); } } }) }
执行结果
欢迎来到 [123 大街]! 对不请,你的 [123 大街] 有问题
-
Python 调用 Rust
-
创建 python 虚拟环境
$ mkdir string_sum $ cd string_sum $ python -m venv .env $ source .env/bin/activate $ pip install maturin
-
创建 rust 工程
$ maturin init ✔ 🤷 What kind of bindings to use? · pyo3 ✨ Done! New project created string_sum