Как я могу реализовать операторы Python в PyO3
Я пытаюсь реализовать векторный класс в ржавчине для моей математической библиотеки.
#[pyclass]
struct Vec2d {
#[pyo3(get, set)]
x: f64,
#[pyo3(get, set)]
y: f64
}
Но я не могу понять, как я могу перегрузить стандартные операторы (+, -, *, /)
Я безуспешно пытался реализовать трейт Add из std::ops
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>
2 ответа
В соответствии с PyO3
Документация,
Объектная модель Python определяет несколько протоколов для различного поведения объектов, таких как протоколы последовательности, сопоставления или числа. PyO3 определяет отдельные черты для каждого из них. Чтобы обеспечить конкретное поведение объекта Python, вам необходимо реализовать конкретную черту для своей структуры.
Важное примечание: каждый блок реализации протокола должен быть помечен # [pyproto
] атрибут.
__add__
, __sub__
и т.д. определены в PyNumberProtocol
Черта.
Чтобы вы могли реализовать PyNumberProtocol
для тебя Vec2d
struct для перегрузки стандартных операций.
#[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 г.):
#[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})
}
}