如何在 PyO3 中实现 python 运算符
How can I implement python operators in PyO3
我正在尝试用 Rust 为我的数学库实现一个向量 class。
#[pyclass]
struct Vec2d {
#[pyo3(get, set)]
x: f64,
#[pyo3(get, set)]
y: f64
}
但我不知道如何重载标准运算符 (+、-、、/)
我尝试从 std::ops 实现 Add trait 但没有成功
impl Add for Vec2d {
type Output = Vec2d;
fn add(self, other: Vec2d) -> Vec2d {
Vec2d{x: self.x + other.x, y: self.y + other.y }
}
}
我还尝试将 __add__
方法添加到 #[pymethods] 块
fn __add__(&self, other: & Vec2d) -> PyResult<Vec2d> {
Ok(Vec2d{x: self.x + other.x, y: self.y + other.y })
}
但还是不行。
使用第二种方法我可以看到该方法在那里,但是 python 不认为它是运算符重载
In [2]: v1 = Vec2d(3, 4)
In [3]: v2 = Vec2d(6, 7)
In [4]: v1 + v2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-08104d7e1232> in <module>()
----> 1 v1 + v2
TypeError: unsupported operand type(s) for +: 'Vec2d' and 'Vec2d'
In [5]: v1.__add__(v2)
Out[5]: <Vec2d object at 0x0000026B74C2B6F0>
根据 PyO3
文档,
Python 的对象模型为不同的对象行为定义了几个协议,如序列、映射或数字协议。 PyO3 为它们中的每一个定义了单独的特征。 要提供特定的 python 对象行为,您需要为您的结构实现特定的特征。
重要说明,每个协议实现块都必须使用 #[pyproto
] 属性进行注释。
__add__
、__sub__
等在 PyNumberProtocol
特征中定义。
因此您可以为您的 Vec2d
结构实现 PyNumberProtocol
以重载标准操作。
#[pyproto]
impl PyNumberProtocol for Vec2d {
fn __add__(&self, other: & Vec2d) -> PyResult<Vec2d> {
Ok(Vec2d{x: self.x + other.x, y: self.y + other.y })
}
}
此解决方案未经测试,完整的工作解决方案请查看@Neven V 的回答。
我会添加这个答案,以免其他人像我一样搜索几个小时。
使用@Abdul Niyas P M 提供的答案,我遇到了以下错误:
error: custom attribute panicked
--> src/vec2.rs:49:1
|
49 | #[pyproto]
| ^^^^^^^^^^
|
= help: message: fn arg type is not supported
事实证明,这个神秘的错误消息隐藏了两个问题。
第一个问题是 __add__
应该取值而不是引用,因此我们删除了 self
和 Vec2
之前的 &
。
这使我们能够摆脱错误消息:
error[E0277]: the trait bound `&vec2::Vec2: pyo3::pyclass::PyClass` is not satisfied
--> src/vec2.rs:47:1
|
47 | #[pyproto]
| ^^^^^^^^^^ the trait `pyo3::pyclass::PyClass` is not implemented for `&vec2::Vec2`
|
当我们指定 self
的类型时,可以揭示第二个问题:
// DOES NOT COMPILE
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __add__(self: Vec2, other: Vec2) -> PyResult<Vec2> {
Ok(Vec2{x: self.x + other.x, y: self.y + other.y})
}
}
编译失败并出现错误消息
error[E0185]: method `__add__` has a `self: <external::vec3::Vec3 as pyo3::class::number::PyNumberAddProtocol<'p>>::Left` declaration in the impl, but not in the trait
--> src/external/vec2.rs:49:5
|
49 | fn __add__(self: Vec2, other: Vec2) -> PyResult<Vec2> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self: <vec2::Vec2 as pyo3::class::number::PyNumberAddProtocol<'p>>::Left` used in impl
|
= note: `__add__` from trait: `fn(<Self as pyo3::class::number::PyNumberAddProtocol<'p>>::Left, <Self as pyo3::class::number::PyNumberAddProtocol<'p>>::Right) -> <Self as pyo3::class::number::PyNumberAddProtocol<'p>>::Result`
这使我们得出了最终的工作解决方案(截至 2020 年 6 月):
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __add__(lhs: Vec2, rhs: Vec2) -> PyResult<Vec2> {
Ok(Vec2{x: lhs.x + rhs.x, y: lhs.y + rhs.y})
}
}
它在 Rust nightly 1.45 下成功编译,并已被检查从 Python 开始工作。
也可以有另一种类型的rhs
:
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __mul__(lhs: Vec2, rhs: f64) -> PyResult<Vec2> {
Ok(Vec3{x: lhs.x * rhs, y: lhs.y * rhs})
}
}
另请注意,self
并不总是问题:
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __neg__(self) -> PyResult<Vec2> {
Ok(Vec3{x: -self.x, y: -self.y})
}
}
我正在尝试用 Rust 为我的数学库实现一个向量 class。
#[pyclass]
struct Vec2d {
#[pyo3(get, set)]
x: f64,
#[pyo3(get, set)]
y: f64
}
但我不知道如何重载标准运算符 (+、-、、/)
我尝试从 std::ops 实现 Add trait 但没有成功
impl Add for Vec2d {
type Output = Vec2d;
fn add(self, other: Vec2d) -> Vec2d {
Vec2d{x: self.x + other.x, y: self.y + other.y }
}
}
我还尝试将 __add__
方法添加到 #[pymethods] 块
fn __add__(&self, other: & Vec2d) -> PyResult<Vec2d> {
Ok(Vec2d{x: self.x + other.x, y: self.y + other.y })
}
但还是不行。
使用第二种方法我可以看到该方法在那里,但是 python 不认为它是运算符重载
In [2]: v1 = Vec2d(3, 4)
In [3]: v2 = Vec2d(6, 7)
In [4]: v1 + v2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-08104d7e1232> in <module>()
----> 1 v1 + v2
TypeError: unsupported operand type(s) for +: 'Vec2d' and 'Vec2d'
In [5]: v1.__add__(v2)
Out[5]: <Vec2d object at 0x0000026B74C2B6F0>
根据 PyO3
文档,
Python 的对象模型为不同的对象行为定义了几个协议,如序列、映射或数字协议。 PyO3 为它们中的每一个定义了单独的特征。 要提供特定的 python 对象行为,您需要为您的结构实现特定的特征。
重要说明,每个协议实现块都必须使用 #[pyproto
] 属性进行注释。
__add__
、__sub__
等在 PyNumberProtocol
特征中定义。
因此您可以为您的 Vec2d
结构实现 PyNumberProtocol
以重载标准操作。
#[pyproto]
impl PyNumberProtocol for Vec2d {
fn __add__(&self, other: & Vec2d) -> PyResult<Vec2d> {
Ok(Vec2d{x: self.x + other.x, y: self.y + other.y })
}
}
此解决方案未经测试,完整的工作解决方案请查看@Neven V 的回答。
我会添加这个答案,以免其他人像我一样搜索几个小时。
使用@Abdul Niyas P M 提供的答案,我遇到了以下错误:
error: custom attribute panicked
--> src/vec2.rs:49:1
|
49 | #[pyproto]
| ^^^^^^^^^^
|
= help: message: fn arg type is not supported
事实证明,这个神秘的错误消息隐藏了两个问题。
第一个问题是 __add__
应该取值而不是引用,因此我们删除了 self
和 Vec2
之前的 &
。
这使我们能够摆脱错误消息:
error[E0277]: the trait bound `&vec2::Vec2: pyo3::pyclass::PyClass` is not satisfied
--> src/vec2.rs:47:1
|
47 | #[pyproto]
| ^^^^^^^^^^ the trait `pyo3::pyclass::PyClass` is not implemented for `&vec2::Vec2`
|
当我们指定 self
的类型时,可以揭示第二个问题:
// DOES NOT COMPILE
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __add__(self: Vec2, other: Vec2) -> PyResult<Vec2> {
Ok(Vec2{x: self.x + other.x, y: self.y + other.y})
}
}
编译失败并出现错误消息
error[E0185]: method `__add__` has a `self: <external::vec3::Vec3 as pyo3::class::number::PyNumberAddProtocol<'p>>::Left` declaration in the impl, but not in the trait
--> src/external/vec2.rs:49:5
|
49 | fn __add__(self: Vec2, other: Vec2) -> PyResult<Vec2> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self: <vec2::Vec2 as pyo3::class::number::PyNumberAddProtocol<'p>>::Left` used in impl
|
= note: `__add__` from trait: `fn(<Self as pyo3::class::number::PyNumberAddProtocol<'p>>::Left, <Self as pyo3::class::number::PyNumberAddProtocol<'p>>::Right) -> <Self as pyo3::class::number::PyNumberAddProtocol<'p>>::Result`
这使我们得出了最终的工作解决方案(截至 2020 年 6 月):
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __add__(lhs: Vec2, rhs: Vec2) -> PyResult<Vec2> {
Ok(Vec2{x: lhs.x + rhs.x, y: lhs.y + rhs.y})
}
}
它在 Rust nightly 1.45 下成功编译,并已被检查从 Python 开始工作。
也可以有另一种类型的rhs
:
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __mul__(lhs: Vec2, rhs: f64) -> PyResult<Vec2> {
Ok(Vec3{x: lhs.x * rhs, y: lhs.y * rhs})
}
}
另请注意,self
并不总是问题:
#[pyproto]
impl PyNumberProtocol for Vec2 {
fn __neg__(self) -> PyResult<Vec2> {
Ok(Vec3{x: -self.x, y: -self.y})
}
}