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

如何无需修改原代码将Rust结构体完整暴露给Python?

无需修改原Rust结构体实现Python互操作的方案

针对你的需求,完全可以在不改动原有Rust结构体代码的前提下,通过PyO3实现Python绑定,同时解决你提到的三个问题。以下是具体实现步骤:

1. 配置依赖(解决无PyO3环境无法编译的问题)

首先在Cargo.toml中将PyO3设为可选feature,这样只有启用该feature时才会编译PyO3相关代码:

[dependencies]
pyo3 = { version = "0.21", optional = true }

2. 保留原有结构体代码

你的原有结构体无需任何修改,比如:

// 这是你不需要改动的固件核心结构体
pub struct RustStruct {
    pub integer: i32,
    pub float: f32,
}

impl RustStruct {
    pub fn new(integer: i32, float: f32) -> Self {
        Self { integer, float }
    }
}

3. 编写独立的PyO3绑定代码

在单独的模块中用条件编译包裹PyO3绑定逻辑,完全隔离原有代码:

方案A:包装类实现(兼容性更好)

通过创建一个包装结构体持有原结构体实例,实现getter/setter,无需修改原结构:

#[cfg(feature = "pyo3")]
mod py_bindings {
    use super::RustStruct;
    use pyo3::prelude::*;

    // 包装类,持有原结构体实例
    #[pyclass]
    struct PyRustStruct(RustStruct);

    #[pymethods]
    impl PyRustStruct {
        // 实现Python侧的构造函数
        #[new]
        fn new(integer: i32, float: f32) -> Self {
            PyRustStruct(RustStruct::new(integer, float))
        }

        // 实现字段的getter/setter,避免逐个修改原结构体成员权限
        #[getter]
        fn integer(&self) -> i32 {
            self.0.integer
        }

        #[setter]
        fn set_integer(&mut self, value: i32) {
            self.0.integer = value;
        }

        #[getter]
        fn float(&self) -> f32 {
            self.0.float
        }

        #[setter]
        fn set_float(&mut self, value: f32) {
            self.0.float = value;
        }
    }

    // 定义Python模块
    #[pymodule]
    fn firmware_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
        m.add_class::<PyRustStruct>()?;
        Ok(())
    }
}

方案B:直接绑定原结构体(PyO3 0.20+支持)

如果你的PyO3版本足够新,可以直接为原结构体实现PyClass绑定,无需包装类:

#[cfg(feature = "pyo3")]
use pyo3::prelude::*;

#[cfg(feature = "pyo3")]
#[pyclass(for = RustStruct)]
impl PyRustStructBindings {
    #[new]
    fn new(integer: i32, float: f32) -> RustStruct {
        RustStruct::new(integer, float)
    }

    #[getter(integer)]
    fn get_integer(slf: &PyCell<'_, RustStruct>) -> i32 {
        slf.borrow().integer
    }

    #[setter(integer)]
    fn set_integer(slf: &PyCell<'_, RustStruct>, value: i32) {
        slf.borrow_mut().integer = value;
    }

    #[getter(float)]
    fn get_float(slf: &PyCell<'_, RustStruct>) -> f32 {
        slf.borrow().float
    }

    #[setter(float)]
    fn set_float(slf: &PyCell<'_, RustStruct>, value: f32) {
        slf.borrow_mut().float = value;
    }
}

#[cfg(feature = "pyo3")]
#[pymodule]
fn firmware_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    m.add_class::<RustStruct>()?;
    Ok(())
}

4. 简化getter/setter编写(可选)

如果结构体字段较多,可以用Rust宏批量生成访问方法,减少重复代码:

#[cfg(feature = "pyo3")]
macro_rules! generate_py_accessors {
    ($wrapper:ident, $field:ident: $ty:ty) => {
        #[getter]
        fn $field(&self) -> $ty {
            self.0.$field
        }

        #[setter]
        fn set_$field(&mut self, value: $ty) {
            self.0.$field = value;
        }
    };
}

// 在包装类的impl中使用宏
#[pymethods]
impl PyRustStruct {
    #[new]
    fn new(integer: i32, float: f32) -> Self {
        PyRustStruct(RustStruct::new(integer, float))
    }

    generate_py_accessors!(PyRustStruct, integer: i32);
    generate_py_accessors!(PyRustStruct, float: f32);
}

效果验证

  • 无PyO3环境编译:直接运行cargo build,PyO3相关代码会被跳过,完全不影响原有固件构建。
  • Python侧使用:启用feature编译为Python模块(maturin develop --features pyo3),即可在Python中实例化并修改结构体:
import firmware_module

obj = firmware_module.PyRustStruct(10, 3.14)
print(obj.integer)  # 输出10
obj.integer = 20
print(obj.integer)  # 输出20

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 17:25:30