You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

方案说明

  1. 代理模式:MutableFloat3Proxy封装对Rust侧Float3元素的可变引用,Python修改代理对象的字段时直接作用于原始数据。
  2. Vec包装器:Float3VecWrapper提供类似Python列表的索引和长度访问能力,内部直接操作Rust的Vec,避免数据克隆。
  3. 自定义访问方法:移除ParentFloat3中data字段的自动getter,改用get_data()返回包装器,确保Python操作的是原始数据而非副本。

该方案既保留了Rust侧连续数据结构的性能优势,又能让Python正确修改原始数据,符合需求。

内容的提问来源于stack exchange,提问作者Krupip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 02:57:32