对多个结构重用 py方法

Re-use pymethods for multiple structs

提问人:Kyle Carow 提问时间:10/19/2023 最后编辑:Kyle Carow 更新时间:10/20/2023 访问量:62

问:

首先是一些背景。我会举个例子,但这个板条箱实际上并不存在:我正在处理的东西要复杂得多,我在下面描述的是一种假设的方法来消除它的复杂性。目前,我的实际 crate 有一个 proc 宏,该宏在每个结构上被调用,它生成一个 impl 块(proc 宏可以接受令牌流以根据需要为单个结构添加更多方法),因此从技术上讲只有一个 impl 块。这使得调试变得非常困难,因为 proc 宏中的任何错误都会显示在调用宏的各种文件中,而没有关于它实际来源的信息。我尝试使用 PyO3 的功能,但无法让我的代码与破坏货物测试的代码进行编译,并且我无法让它在我的一生中工作#[pymethods]#[pymethods]multiple-pymethods

问题:

假设我在 Rust 中有一个 Python 扩展箱,它有多个结构,所有结构都具有完全相同的 serde 序列化和反序列化函数,例如 、 等。这些通过 impl 块向 Python 公开。.to_yaml().from_yaml()#[pymethods]

由于所有这些方法的代码完全相同,我希望能够在一个位置定义它们,并将它们应用于所有结构。如果没有单一的需求,我会简单地为我的所有结构创建一个特征和它。通过使用带有宏或其他东西的方法扩展每个结构体的 impl 块是否可行?还有其他想法吗?#[pymethods]impl#[pymethods]

编辑:有关以下问题的更多详细信息:multiple-pymethods

添加该功能会导致 macOS 上出现链接错误,可按照以下说明在板条箱根目录添加链接错误来修复该错误: https://pyo3.rs/main/building_and_distribution#macosmultiple-pymethods.cargo/config.toml

现在我返回以下错误:cargo testdyld[9884]: symbol not found in flat namespace (_PyBaseObject_Type)

编辑:我为假设代码添加了一个示例:

Cargo.toml:

[package]
name = "pyo3-testing"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "pyo3_testing"
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0.75"
pyo3 = { version = "0.19.0", features = ["anyhow", "extension-module"] }
# pyo3 = { version = "0.19.0", features = ["anyhow", "extension-module", "multiple-pymethods"] }
serde = { version = "1.0.189", features = ["derive"] }
serde_yaml = "0.9.25"

lib.rs:

use anyhow;
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use serde_yaml;

/// A Python module implemented in Rust.
#[pymodule]
fn pyo3_testing(_py: Python, m: &PyModule) -> PyResult<()> {
    // m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
    m.add_class::<Point2D>()?;
    Ok(())
}

#[pyclass]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Point2D {
    pub x: f64,
    pub y: f64,
}

#[pyclass]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Point3D {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

#[pymethods]
impl Point2D {
    // This method is unique to Point2D
    #[new]
    fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    // DUPLICATE METHOD
    fn to_yaml(&self) -> anyhow::Result<String> {
        Ok(serde_yaml::to_string(self)?)
    }

    // DUPLICATE METHOD
    #[staticmethod]
    fn from_yaml(yaml: &str) -> anyhow::Result<Self> {
        Ok(serde_yaml::from_str(yaml)?)
    }
}

#[pymethods]
impl Point3D {
    // This method is unique to Point3D
    #[new]
    fn new(x: f64, y: f64, z: f64) -> Self {
        Self { x, y, z }
    }

    // DUPLICATE METHOD
    fn to_yaml(&self) -> anyhow::Result<String> {
        Ok(serde_yaml::to_string(self)?)
    }

    // DUPLICATE METHOD
    #[staticmethod]
    fn from_yaml(yaml: &str) -> anyhow::Result<Self> {
        Ok(serde_yaml::from_str(yaml)?)
    }
}

#[cfg(test)]
mod point2d_tests {
    use super::*;

    #[test]
    fn test_to_yaml() -> anyhow::Result<()> {
        let expected_yaml = "x: 1.0\ny: 2.0\n";

        let point = Point2D::new(1.0, 2.0);
        let actual_yaml = point.to_yaml()?;

        anyhow::ensure!(
            actual_yaml == expected_yaml,
            "{actual_yaml:?} != {expected_yaml:?}",
        );
        Ok(())
    }

    #[test]
    fn test_from_yaml() -> anyhow::Result<()> {
        let expected_point = Point2D::new(1.0, 2.0);

        let yaml = "x: 1.0\ny: 2.0\n";
        let actual_point = Point2D::from_yaml(yaml)?;

        anyhow::ensure!(
            actual_point == expected_point,
            "{actual_point:?} != {expected_point:?}",
        );
        Ok(())
    }
}

#[cfg(test)]
mod point3d_tests {
    use super::*;

    #[test]
    fn test_to_yaml() -> anyhow::Result<()> {
        let expected_yaml = "x: 1.0\ny: 2.0\nz: 3.0\n";

        let point = Point3D::new(1.0, 2.0, 3.0);
        let actual_yaml = point.to_yaml()?;

        anyhow::ensure!(
            actual_yaml == expected_yaml,
            "{actual_yaml:?} != {expected_yaml:?}",
        );
        Ok(())
    }

    #[test]
    fn test_from_yaml() -> anyhow::Result<()> {
        let expected_point = Point3D::new(1.0, 2.0, 3.0);

        let yaml = "x: 1.0\ny: 2.0\nz: 3.0\n";
        let actual_point = Point3D::from_yaml(yaml)?;

        anyhow::ensure!(
            actual_point == expected_point,
            "{actual_point:?} != {expected_point:?}",
        );
        Ok(())
    }
}

.cargo/config.toml:

[target.x86_64-apple-darwin]
rustflags = [
  "-C", "link-arg=-undefined",
  "-C", "link-arg=dynamic_lookup",
]

[target.aarch64-apple-darwin]
rustflags = [
  "-C", "link-arg=-undefined",
  "-C", "link-arg=dynamic_lookup",
]
PYO3型

评论

1赞 Chayim Friedman 10/19/2023
有什么问题?multiple-pymethods
0赞 Kyle Carow 10/19/2023
我想我说它不编译的描述并不完全正确,但我找不到任何失败的解决方法。测试对于这个代码库至关重要,因为它相当庞大。我添加了一个示例,因此您可以测试添加到功能并运行cargo testmultiple-pymethodscargo test

答: 暂无答案