能否将Rust创建的Polars DataFrame传递给Python且避免拷贝?
Rust 侧持有 Polars DataFrame,供 Python 插件只读访问的方案
完全可行,核心思路是通过引用共享+自定义PyO3包装类型,让Python仅获得DataFrame的访问权,而所有权始终留在Rust侧,避免拷贝/移动数据。
关键实现要点
- 用
Arc<DataFrame>包装Rust侧的DataFrame,利用原子引用计数实现安全的跨语言共享,Rust始终持有所有权。 - 基于PyO3自定义
PyClass类型,将Arc<DataFrame>封装在内部,仅暴露只读访问方法给Python,禁止所有权转移。 - 无需依赖
pyo3-polars的默认转换逻辑(该逻辑确实会触发数据拷贝),自定义包装可以完全控制数据流向。
示例代码片段
Rust 侧(核心实现)
use pyo3::prelude::*; use polars::prelude::*; use std::sync::Arc; // 自定义Python可访问的DataFrame包装类,仅持有Rust侧DataFrame的共享引用 #[pyclass] struct SharedDataFrame { inner: Arc<DataFrame>, } #[pymethods] impl SharedDataFrame { // 暴露只读属性:获取行数 #[getter] fn row_count(&self) -> usize { self.inner.height() } // 读取指定列的数据(返回拷贝后的Vec,若仅需计算可在Rust侧完成以避免拷贝) fn get_column(&self, col_name: &str) -> PyResult<Vec<Option<f64>>> { let col = self.inner.column(col_name)?; let series = col.f64()?; Ok(series.into_iter().collect()) } // 在Rust侧执行计算,直接返回结果,数据全程不离开Rust fn sum_column(&self, col_name: &str) -> PyResult<Option<f64>> { let col = self.inner.column(col_name)?; Ok(col.sum()) } } // 初始化Python插件模块 #[pymodule] fn rust_polars_ext(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::<SharedDataFrame>()?; // 示例:提供一个从Rust侧创建并返回SharedDataFrame的函数 #[pyfn(m)] fn create_test_dataframe(_py: Python) -> SharedDataFrame { let df = df!( "id" => [1, 2, 3, 4], "value" => [10.5, 20.7, 30.2, 40.9] ).unwrap(); SharedDataFrame { inner: Arc::new(df) } } Ok(()) }
Python 侧(插件使用)
import rust_polars_ext # 从Rust侧获取共享的DataFrame包装实例 df = rust_polars_ext.create_test_dataframe() # 访问只读属性 print(f"行数: {df.row_count()}") # 读取列数据(按需拷贝,若仅做计算建议调用Rust侧方法) values = df.get_column("value") print(f"value列数据: {values}") # 直接调用Rust侧的计算逻辑,数据不离开Rust sum_val = df.sum_column("value") print(f"value列求和: {sum_val}")
注意事项
- 线程安全:Polars的DataFrame默认是不可变的,结合
Arc可安全跨线程、跨语言共享;若需支持修改,可改用Arc<RwLock<DataFrame>>,但需注意同步逻辑。 - 只读约束:通过仅暴露只读方法,确保Python无法修改Rust侧的原始DataFrame,完全符合“数据留存于Rust侧”的需求。
- 性能优化:对于复杂计算,尽量在Rust侧实现后暴露结果给Python,避免大量数据拷贝开销。
内容的提问来源于stack exchange,提问作者nu11ptr
相关产品推荐
相关产品推荐

