__str__ 使用 pyo3 从 rust 移植到 python 的 class 函数没有在打印中使用

__str__ function of class ported from rust to python using pyo3 doesn't get used in print

我正在使用 pyo3 rust crate(版本 0.11.1)以将 rust 代码移植到 cpython(版本 3.8.2)代码中。我创建了一个名为 my_class 的 class 并定义了以下函数:new__str____repr__.

TL;DR: The __str__ function exists on a class ported from rust using the pyo3 crate, but doesn't get printed when just using print(obj), and instead having to write print(obj.__str__())

my_class 定义在这里:

use pyo3::prelude::*;

#[pyclass]
struct my_class {
    #[pyo3(get, set)]
    num: i32,
    #[pyo3(get, set)]
    debug: bool,
}

#[pymethods]
impl my_class {
    #[new]
    fn new(num: i32, debug: bool) -> Self {
        my_class {num, debug}
    }
    fn __str__(&self) -> PyResult<String>   {
        Ok(format!("[__str__] Num: {}, Debug: {}", self.num, self.debug))
    }

    fn __repr__(&self) -> PyResult<String> {
        Ok(format!("[__repr__] Num: {}, Debug: {}", self.num, self.debug))
    }
}

#[pymodule]
fn pymspdb(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<my_class>()?;
    Ok(())
}

我构建这个(进入发布模式),并使用以下代码测试代码:

from my_module import my_class

def main():
    dsa = my_class(1, True)
    print(dsa)
    print(dsa.__str__())
    
    
if __name__ == "__main__":
    main()

当运行测试python代码时,我得到以下输出:

<my_class object at 0x7fb7828ae950>
[__str__] Num: 1, Debug: true

现在我想到了可能的解决方案。一种解决方案可能是 pyo3 rust crate 实际上充当代理,为了将 classes 移植到 python 中,可能会实现某种对象,将所有操作转移到移植的 class .所以它可能不会实现自己的 __str__ 因此不会给我我想要的东西。 我想到的第二个可能的解决方案是我可能没有正确地重载 __str__ 函数,因此当 python 尝试使用打印函数时,它不会访问正确的函数,只会执行默认行为。

感谢您到目前为止的阅读,希望我能找到答案,因为我在网上找不到任何相关内容。

我很确定这是因为您需要通过 PyObjectProtocol trait.

来实现这些方法

许多Python __magic__ 方法对应于类型对象内存布局中的C 级别function pointer slots。 C 实现的类型需要在槽中提供一个函数指针,Python 会自动生成一个方法来包装指针以供显式方法调用。在 Python 中实现的类型将自动插入函数指针,委托给魔法方法。

Python 内部通常会查找函数指针而不是相应的魔术方法,如果 Python 没有找到函数指针,它的行为就好像该方法没有'不存在。这就是为什么,例如,你必须使用 #[new] 来标记你的构造函数,而不是实现一个 __new__ 静态方法。

__str____repr__ 也对应于函数指针 - 具体来说,tp_str and tp_repr。如果您只是尝试将它们作为常规方法来实现,pyo3 将不会生成所需的函数指针。 PyObjectProtocol 是为此要经过的 pyo3 接口。

这是一种可能的解决方案……

python 导入 rust 代码…

from my_module import my_class

def main():
    dsa = my_class(1, True)
    print(dsa)
    print(dsa.__str__())


if __name__ == "__main__":
    main()

Rust 源代码…

use pyo3::prelude::*;
use pyo3::PyObjectProtocol;

#[pyclass]
struct my_class {
    #[pyo3(get, set)]
    num: i32,
    #[pyo3(get, set)]
    debug: bool,
}

#[pymethods]
impl my_class {
    #[new]
    fn new(num: i32, debug: bool) -> Self {
        my_class {num, debug}
    }
    fn __str__(&self) -> PyResult<String>   {
        Ok(format!("[__str__] Num: {}, Debug: {}", self.num, self.debug))
    }

    fn __repr__(&self) -> PyResult<String> {
        Ok(format!("[__repr__] Num: {}, Debug: {}", self.num, self.debug))
    }
}

#[pyproto]
impl PyObjectProtocol for my_class {
    fn __str__(&self) -> PyResult<String>   {
        Ok(format!("[__str__] Num: {}, Debug: {}", self.num, self.debug))
    }

    fn __repr__(&self) -> PyResult<String> {
        Ok(format!("[__repr__] Num: {}, Debug: {}", self.num, self.debug))
    }
}

#[pymodule]
fn pymspdb(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_class::<my_class>()?;
    Ok(())
}