如何通过PyO3实现Rust向Python传递回调函数
实现Rust通过PyO3向Python传递可多次调用的回调函数
核心思路
PyO3通过wrap_pyfunction!宏可以将Rust函数包装为Python可调用对象,Python侧能在任意时机多次调用该对象,Rust函数会在每次调用时处理传入的数据。关键是保证回调函数执行时持有Python GIL(PyO3的包装宏会自动处理这一点)。
完整代码示例
1. Python侧代码(test.py)
class Test: def test_fn(self, callback): # 模拟不同业务时机调用回调 callback("初始化完成,第一次回调") print("Python执行中间逻辑:处理数据") callback("数据处理完成,第二次回调") print("Python执行收尾逻辑:资源清理") callback("任务结束,第三次回调")
2. Rust侧配置与代码
首先在Cargo.toml中添加PyO3依赖:
[package] name = "rust_py_callback" version = "0.1.0" edition = "2021" [dependencies] pyo3 = { version = "0.20", features = ["extension-module"] }
方案一:作为Python扩展模块(src/lib.rs)
use pyo3::prelude::*; use pyo3::types::PyModule; // Rust回调函数:接收Python传入的字符串,处理后返回结果 fn my_callback(py: Python, msg: &str) -> PyResult<()> { println!("[Rust回调] 收到消息:{}", msg); // 这里可以添加任意Rust逻辑,比如修改本地状态、调用其他Rust函数等 Ok(()) } // 将Rust函数包装为Python可调用对象 fn wrap_rust_callback(py: Python) -> PyResult<PyObject> { let py_callback = wrap_pyfunction!(my_callback, py)?; Ok(py_callback.into()) } // 定义Python可调用的扩展函数,用于触发整个流程 #[pyfunction] fn run_python_test(py: Python) -> PyResult<()> { // 加载本地的test.py模块 let test_module = PyModule::import(py, "test")?; // 获取Test类并创建实例 let test_class = test_module.getattr("Test")?; let test_instance = test_class.call0()?; // 生成Python可调用的回调对象 let callback = wrap_rust_callback(py)?; // 调用Python类的方法,传入回调 test_instance.call_method1("test_fn", (callback,))?; Ok(()) } // 定义Python扩展模块 #[pymodule] fn rust_py_callback(_py: Python, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(run_python_test, m)?)?; Ok(()) }
方案二:Rust直接启动Python解释器(src/main.rs)
如果不需要做Python扩展,而是让Rust程序直接加载Python逻辑:
use pyo3::prelude::*; use pyo3::types::PyModule; fn my_callback(py: Python, msg: &str) -> PyResult<()> { println!("[Rust回调] 收到消息:{}", msg); Ok(()) } fn wrap_rust_callback(py: Python) -> PyResult<PyObject> { let py_callback = wrap_pyfunction!(my_callback, py)?; Ok(py_callback.into()) } fn main() -> PyResult<()> { // 初始化Python解释器并持有GIL Python::with_gil(|py| { let test_module = PyModule::import(py, "test")?; let test_class = test_module.getattr("Test")?; let test_instance = test_class.call0()?; let callback = wrap_rust_callback(py)?; test_instance.call_method1("test_fn", (callback,))?; Ok(()) }) }
编译与运行(不使用maturin)
Linux/macOS环境
- 编译Rust代码:
cargo build --release
- 若使用扩展模块方案,重命名动态库为Python可识别的名称:
cp target/release/librust_py_callback.so rust_py_callback.so
- 运行Python脚本(扩展模块方案):
# main.py import rust_py_callback rust_py_callback.run_python_test()
python main.py
- 若使用Rust直接启动方案,直接运行:
cargo run --release
Windows环境
- 编译Rust代码:
cargo build --release
- 若使用扩展模块方案,重命名动态库:
copy target\release\rust_py_callback.dll rust_py_callback.pyd
- 后续运行步骤与Linux/macOS一致。
关键注意事项
- GIL管理:
wrap_pyfunction!宏自动处理GIL的获取与释放;如果自定义PyClass作为回调,需在方法中通过Python::with_gil或参数中的Python实例确保GIL持有。 - 状态共享:如果需要在回调中修改Rust的全局/共享状态,单线程场景用
RefCell,多线程场景用Mutex,但多线程需注意GIL限制(同一时间仅一个线程能持有GIL)。 - 参数类型:示例使用字符串,PyO3支持自动转换大多数基础类型(i32、f64、bool等),自定义类型需实现
PyFromPyObject或IntoPy<PyObject>trait。
后续建议
- 虽然当前未使用maturin,但它能自动处理跨平台动态库命名、Python环境适配,简化扩展模块发布流程,后续可尝试引入。
- 若需异步回调场景(比如Python异步任务调用Rust函数),可结合
pyo3-asyncio和async-trait实现异步回调逻辑。
内容的提问来源于stack exchange,提问作者V.Rashkov
相关产品推荐
相关产品推荐

