【问题标题】:TypeError in Python using PyAny in reflected numeric emulator (e.g. __radd__) for a Rust pyo3 pyclass structPython 中的 TypeError 使用 PyAny 在反射数字模拟器(例如 __radd__)中用于 Rust pyo3 pyclass 结构
【发布时间】:2020-09-09 10:35:49
【问题描述】:

我使用 pyo3 为 python 创建了一个 Rust 库。这包含一个 pyclass 结构,它实现了几个 PyNumberProtocol 方法,如 __add____sub__ 等......以允许 + 和 - 等 python 运算符在该类上工作。我在其中大多数中使用 PyAny 作为“其他”对象,因为我想支持许多不同的类型。这很好,但是当我尝试实现像__radd____rsub__ 这样的反射方法时,python 会抛出一个 TypeError。抛出的 TypeError 没有参数或消息,它只是一个空的 TypeError。如果我调用myitem.__radd__(other)other + myitem 失败,则该方法本身有效。我删除了除 __add____radd__ 之外的所有内容,例如 i64(下面的 TestClass1)。

我可以为特定类型实现反射方法,例如 i64(参见下面的 TestClass2)。但显然这不允许任何不同的类型(浮点数、列表、类等......)。我找不到任何可以工作的泛型类型,也找不到任何重载__radd__ 方法的方法。所以我的问题是,有没有办法实现__radd__ 来接受来自python 的多种类型?我对 Rust 很陌生,所以我可能错过了一些明显的东西......

Rust 示例库:

use pyo3::exceptions::TypeError;
use pyo3::prelude::*;
use pyo3::PyNumberProtocol;

macro_rules! create_test_class {
    ($name: ident) => {
        #[pyclass]
        #[derive(PartialEq, Debug, Clone)]
        pub struct $name {
            #[pyo3(get, set)]
            value: i64,
        }
        #[pymethods]
        impl $name {
            #[new]
            pub fn from_value(value: i64) -> $name {
                $name { value: value }
            }
        }
    };
}

create_test_class!(TestClass1);
create_test_class!(TestClass2);

#[pyproto]
impl PyNumberProtocol for TestClass1 {
    fn __add__(lhs: TestClass1, rhs: &PyAny) -> PyResult<TestClass1> {
        let pynum_result: Result<i64, _> = rhs.extract();
        if let Ok(pynum) = pynum_result {
            Ok(TestClass1 {
                value: lhs.value + pynum,
            })
        } else {
            Err(TypeError::py_err("Not implemented for this type!"))
        }
    }
    fn __radd__(self, other: &PyAny) -> PyResult<TestClass1> {
        let pynum_result: Result<i64, _> = other.extract();
        if let Ok(pynum) = pynum_result {
            Ok(TestClass1 {
                value: self.value + pynum,
            })
        } else {
            Err(TypeError::py_err("Not implemented for this type!"))
        }
    }
}

#[pyproto]
impl PyNumberProtocol for TestClass2 {
    fn __radd__(self, other: i64) -> PyResult<TestClass2> {
        Ok(TestClass2 {
            value: self.value + other,
        })
    }
}

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

Python 示例,除最后一行外,所有打印语句都按预期工作:

from test_class import TestClass1, TestClass2

tc2 = TestClass2(10)
print(tc2.__radd__(3).value)  # 13
print((3 + tc2).value)        # 13
try:
    3.0 + tc2                 # expected TypeError
except TypeError as e:
    print(repr(e))            # TypeError("'float' object cannot be interpreted as an integer")

tc1 = TestClass1(10)
print((tc1 + 3).value)        # 13
print(tc1.__radd__(3).value)  # 13
print((3 + tc1).value)        # unexpected, empty TypeError 

我正在使用 Rust 1.45.2、pyo3 0.11.1、python 3.7.3

【问题讨论】:

    标签: python rust pyo3


    【解决方案1】:

    经过更多挖掘,它看起来像是当前 pyo3 版本的限制: https://github.com/PyO3/pyo3/issues/844

    而且它与 PyAny 无关,我的测试太简单了。 TestClass2 工作不是因为它使用 i64 而不是 &PyAny,而是因为它没有 __add__!我添加了一个简单的__add__ 方法,果然打破了它。

    无论如何,从 github 讨论中的闲聊看来,这将在 pyo3 0.12 中运行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-16
      相关资源
      最近更新 更多