Как я могу реализовать операторы 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})
    }
}
Другие вопросы по тегам