3

Minimal example that works but isn't quite what I want:

use std::collections::HashMap;

use pyo3::class::basic::CompareOp;
use pyo3::class::PyObjectProtocol;
use pyo3::prelude::*;

#[pyclass]
struct MyStruct {
  foo: HashMap<i32, Vec<i32>>,
}

#[pyproto]
impl PyObjectProtocol for MyStruct {
  fn __richcmp__(&self, other: PyRef<MyStruct>, op: CompareOp) -> Py<PyAny> {
    let gil = Python::acquire_gil();
    let py = gil.python();

    if let CompareOp::Eq = op {
      let other_foo = &(other.foo);
      let res = self.foo == *other_foo;
      return res.into_py(py);
    }
    
    py.NotImplemented();
  }
}

So this will take a reference to a MyStruct living in the python heap and run the comparison on it, and my understanding is that no copying / cloning will happen.

But this isn't quite the pythonic way. The argument other doesn't have to be of the same type. Instead of raising a TypeError, the comparison method should just return NotImplemented.

I'm trying to change the signature so that

  • I can accept arbitrary python objects. I thought &PyAny would be just right
  • I can then check the type, or try the conversion, but without cloning the object from the Python heap over to Rust's heap.

Is there any way to convert, with checking of course, a &PyAny into a PyRef<MyStruct>?

cadolphs
  • 9,014
  • 1
  • 24
  • 41
  • Actually it seems like the `pyproto` macro was recently fixed to return `NotImplemented` when the type conversion to the `PyRef` fails. This is awesome. But my _general_ question about best practice remains: How to accept arbitrary object from python and cast to a reference to a python class defined in Rust? – cadolphs Jun 16 '21 at 16:49

1 Answers1

2

Huh, turns out the answer is quite simple. The reason I couldn't get it to work is that there were changes to PyO3.

In the past, the trait FromPyObject was automatically implemented for &T when you had a pyclass T.

But not so any more.

Here's the correct way:

fn do_stuff(any: &PyAny) {
  let res: PyResult<PyRef<MyStruct>> = any.extract();
  if res.is_err() { // whoops; wasn't a MyStruct after all }
}

Source: https://pyo3.rs/v0.13.2/migration.html

Object extraction

For PyClass types T, &T and &mut T no longer have FromPyObject implementations. Instead you should extract PyRef<T> or PyRefMut<T>, respectively. If T implements Clone, you can extract T itself. In addition, you can also extract &PyCell<T>, though you rarely need it.

cadolphs
  • 9,014
  • 1
  • 24
  • 41