Python 在反射数字模拟器(例如 __radd__)中对 Rust pyo3 pyclass 结构使用 PyAny 时出现 TypeError
TypeError in Python using PyAny in reflected numeric emulator (e.g. __radd__) for a Rust pyo3 pyclass struct
我使用 pyo3 为 python 创建了一个 Rust 库。这包含一个 pyclass 结构,该结构实现了多个 PyNumberProtocol 方法,如 __add__
、__sub__
等...以允许 python 运算符,如 + 和 - 在 class。我在其中大部分中使用 PyAny 作为 'other' 对象,因为我想支持许多不同的类型。这工作正常,但是当我尝试实现 __radd__
和 __rsub__
等反射方法时,python 抛出 TypeError。抛出的 TypeError 没有参数或消息,它只是一个空的 TypeError。如果我调用 myitem.__radd__(other)
但 other + myitem
失败,则该方法本身有效。我去掉了除 __add__
和 __radd__
之外的所有内容,例如 i64(下面的 TestClass1)。
我可以为特定类型实现反射方法,例如 i64(请参阅下面的 TestClass2)。但显然这不允许任何不同的类型(浮点数、列表、classes 等)。我找不到任何可用的通用类型,也找不到任何重载 __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
经过更多的挖掘,它看起来像是当前版本的 pyo3 的限制:
https://github.com/PyO3/pyo3/issues/844
也和PyAny无关,我测试的太简单了。 TestClass2 起作用不是因为它使用 i64 而不是 &PyAny,而是因为它没有 __add__
!我添加了一个简单的 __add__
方法,果然破坏了它。
无论如何,从 github 讨论中的喋喋不休来看,这似乎适用于 pyo3 0.12。
我使用 pyo3 为 python 创建了一个 Rust 库。这包含一个 pyclass 结构,该结构实现了多个 PyNumberProtocol 方法,如 __add__
、__sub__
等...以允许 python 运算符,如 + 和 - 在 class。我在其中大部分中使用 PyAny 作为 'other' 对象,因为我想支持许多不同的类型。这工作正常,但是当我尝试实现 __radd__
和 __rsub__
等反射方法时,python 抛出 TypeError。抛出的 TypeError 没有参数或消息,它只是一个空的 TypeError。如果我调用 myitem.__radd__(other)
但 other + myitem
失败,则该方法本身有效。我去掉了除 __add__
和 __radd__
之外的所有内容,例如 i64(下面的 TestClass1)。
我可以为特定类型实现反射方法,例如 i64(请参阅下面的 TestClass2)。但显然这不允许任何不同的类型(浮点数、列表、classes 等)。我找不到任何可用的通用类型,也找不到任何重载 __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
经过更多的挖掘,它看起来像是当前版本的 pyo3 的限制: https://github.com/PyO3/pyo3/issues/844
也和PyAny无关,我测试的太简单了。 TestClass2 起作用不是因为它使用 i64 而不是 &PyAny,而是因为它没有 __add__
!我添加了一个简单的 __add__
方法,果然破坏了它。
无论如何,从 github 讨论中的喋喋不休来看,这似乎适用于 pyo3 0.12。