PyO3中如何修改Python创建后传入Rust的Rust类型数据
问题:Python修改Rust侧Vec元素不生效
目录结构
- python * main.py - src * lib.rs * Cargo.toml * pyproject.toml
仅lib.rs和main.py为自定义文件,其余由maturin init生成。
原代码与输出
原lib.rs代码
use pyo3::prelude::*; #[pyclass] #[derive(Clone)] struct Float3{ #[pyo3(get, set)] x : f64, #[pyo3(get, set)] y : f64, #[pyo3(get, set)] z : f64, } #[pymethods] impl Float3 { #[new] fn py_new(x : f64, y : f64, z : f64) -> Self { Float3 { x, y, z} } } #[pyclass] #[derive(Clone)] struct ParentFloat3 { #[pyo3(get, set)] data: Vec<Float3>, } #[pymethods] impl ParentFloat3 { #[new] fn py_new(data : Vec<Float3>) -> Self { ParentFloat3 {data} } } #[pyclass] #[derive(Clone)] struct ParentFunctions { python_functions :Vec<PyObject> } #[pymethods] impl ParentFunctions { #[new] fn py_new(python_functions : Vec<PyObject>) -> Self { ParentFunctions {python_functions } } } #[pyfunction] fn test_fn<'py>(parent_float3 : &Bound<'_, ParentFloat3>, parent_functions : &Bound<'_, ParentFunctions>) -> PyResult<i32>{ for python_function in parent_functions.borrow().python_functions.iter(){ python_function.call1(parent_float3.py(), (parent_float3.borrow_mut(),))?; } Ok(10) } /// A Python module implemented in Rust. #[pymodule] fn test_pyo3(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::<Float3>()?; m.add_class::<ParentFloat3>()?; m.add_class::<ParentFunctions>()?; m.add_function(wrap_pyfunction!(test_fn, m)?)?; Ok(()) }
原main.py代码
import test_pyo3 def modify_parent_float_3(parent : test_pyo3.ParentFloat3): for value in parent.data: value.x += 1.0 parent.data[0].x += 1.0 pass def main(): temp_float3s = test_pyo3.ParentFloat3([test_pyo3.Float3(0,0,0)]) temp_functions = test_pyo3.ParentFunctions([ modify_parent_float_3 ]) print(temp_float3s.data[0].x, temp_float3s.data[0].y, temp_float3s.data[0].z) test_pyo3.test_fn(temp_float3s, temp_functions) print(temp_float3s.data[0].x, temp_float3s.data[0].y, temp_float3s.data[0].z) pass if __name__ == '__main__': main()
输出结果
0.0 0.0 0.0 0.0 0.0 0.0
问题核心
尝试修改parent.data中的元素但未生效,直接修改单个Float3对象有效,但修改Vec<Float3>中的元素无效。要求data必须是Rust侧的连续数据结构,不能使用Python原生类型(如PyList、numpy数组)。
问题原因
默认情况下,PyO3对#[pyclass]中Vec<T>类型的字段生成的getter会返回原数据的克隆副本:因为Float3实现了Clone,每次访问parent.data时,PyO3都会将Rust的Vec<Float3>转换为Python列表,其中每个Float3元素都是原数据的克隆。修改这个副本中的元素自然不会影响Rust侧的原始数据。
解决方案
通过自定义代理对象和包装器,让Python直接操作Rust侧的原始Vec元素,而非副本。
修改后的lib.rs代码
use pyo3::prelude::*; #[pyclass] #[derive(Clone)] struct Float3{ #[pyo3(get, set)] x : f64, #[pyo3(get, set)] y : f64, #[pyo3(get, set)] z : f64, } #[pymethods] impl Float3 { #[new] fn py_new(x : f64, y : f64, z : f64) -> Self { Float3 { x, y, z} } } // 代理对象:封装对原Float3元素的可变引用 #[pyclass] struct MutableFloat3Proxy<'a> { elem: &'a mut Float3, } #[pymethods] impl<'a> MutableFloat3Proxy<'a> { #[getter] fn x(&self) -> f64 { self.elem.x } #[setter] fn set_x(&mut self, value: f64) { self.elem.x = value; } #[getter] fn y(&self) -> f64 { self.elem.y } #[setter] fn set_y(&mut self, value: f64) { self.elem.y = value; } #[getter] fn z(&self) -> f64 { self.elem.z } #[setter] fn set_z(&mut self, value: f64) { self.elem.z = value; } } // Vec<Float3>包装器:提供类似Python列表的索引访问 #[pyclass] struct Float3VecWrapper<'a> { data: &'a mut Vec<Float3>, } #[pymethods] impl<'a> Float3VecWrapper<'a> { fn __getitem__(&mut self, index: usize) -> PyResult<Bound<'_, MutableFloat3Proxy<'a>>> { let py = Python::with_gil(); let elem = self.data.get_mut(index) .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyIndexError, _>("Index out of bounds"))?; Ok(Bound::new(py, MutableFloat3Proxy { elem })?) } fn __len__(&self) -> usize { self.data.len() } } #[pyclass] #[derive(Clone)] struct ParentFloat3 { // 移除自动生成的get/set,改用自定义访问方法 data: Vec<Float3>, } #[pymethods] impl ParentFloat3 { #[new] fn py_new(data : Vec<Float3>) -> Self { ParentFloat3 {data} } // 获取原始数据的可变包装器 fn get_data(&mut self) -> PyResult<Bound<'_, Float3VecWrapper>> { let py = Python::with_gil(); Ok(Bound::new(py, Float3VecWrapper { data: &mut self.data })?) } // 快速获取单个元素的代理 fn get_element(&mut self, index: usize) -> PyResult<Bound<'_, MutableFloat3Proxy>> { let py = Python::with_gil(); let elem = self.data.get_mut(index) .ok_or_else(|| PyErr::new::<pyo3::exceptions::PyIndexError, _>("Index out of bounds"))?; Ok(Bound::new(py, MutableFloat3Proxy { elem })?) } } #[pyclass] #[derive(Clone)] struct ParentFunctions { python_functions :Vec<PyObject> } #[pymethods] impl ParentFunctions { #[new] fn py_new(python_functions : Vec<PyObject>) -> Self { ParentFunctions {python_functions } } } #[pyfunction] fn test_fn<'py>(parent_float3 : &Bound<'_, ParentFloat3>, parent_functions : &Bound<'_, ParentFunctions>) -> PyResult<i32>{ for python_function in parent_functions.borrow().python_functions.iter(){ python_function.call1(parent_float3.py(), (parent_float3.borrow_mut(),))?; } Ok(10) } #[pymodule] fn test_pyo3(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::<Float3>()?; m.add_class::<ParentFloat3>()?; m.add_class::<ParentFunctions>()?; m.add_class::<MutableFloat3Proxy>()?; m.add_class::<Float3VecWrapper>()?; m.add_function(wrap_pyfunction!(test_fn, m)?)?; Ok(()) }
修改后的main.py代码
import test_pyo3 def modify_parent_float_3(parent : test_pyo3.ParentFloat3): # 获取原始数据的可变包装器 data = parent.get_data() # 遍历修改元素 for i in range(len(data)): elem = data[i] elem.x += 1.0 # 直接修改第一个元素 data[0].x += 1.0 def main(): temp_float3s = test_pyo3.ParentFloat3([test_pyo3.Float3(0,0,0)]) temp_functions = test_pyo3.ParentFunctions([ modify_parent_float_3 ]) print(temp_float3s.get_element(0).x, temp_float3s.get_element(0).y, temp_float3s.get_element(0).z) test_pyo3.test_fn(temp_float3s, temp_functions) print(temp_float3s.get_element(0).x, temp_float3s.get_element(0).y, temp_float3s.get_element(0).z) if __name__ == '__main__': main()
运行结果
0.0 0.0 0.0 2.0 0.0 0.0
方案说明
- 代理模式:
MutableFloat3Proxy封装对Rust侧Float3元素的可变引用,Python修改代理对象的字段时直接作用于原始数据。 - Vec包装器:
Float3VecWrapper提供类似Python列表的索引和长度访问能力,内部直接操作Rust的Vec,避免数据克隆。 - 自定义访问方法:移除
ParentFloat3中data字段的自动getter,改用get_data()返回包装器,确保Python操作的是原始数据而非副本。
该方案既保留了Rust侧连续数据结构的性能优势,又能让Python正确修改原始数据,符合需求。
内容的提问来源于stack exchange,提问作者Krupip
相关产品推荐
相关产品推荐

