pythonrustpyo3python-bindings

Embedded a #[pyclass] in another #[pyclass]


I am trying to implement a cache for the private variable of any python class.

Let's suppose I have this:

#[pyclass]
struct ClassA {
    pv: Py<PyAny>, // GIL independent type, storable in another #[pyclass]
    field_a: Py<PyAny>,
}

#[pyclass]
struct ClassB {
    class_a: Py<ClassA>,
    field_b: Py<PyAny>,
}

In the impls of ClassA, I have a method attempting to 'put' a reference of the ClassA object into the ClassB's class_a field. And return a newly instantiated ClassB object.

#[pymethods]
impl ClassA {
    fn ret_class_b(&self, field_b: &PyAny) -> PyResult<ClassB> {
        let class_a: Py<ClassA> = Py::clone_ref(self, field_b.py());
        ClassB {
            class_a: class_a,
            field_b: field_b.into_py(),
        }
    }
}

The above does not compile.

The issue is how do I get &Py<ClassA> from the receiver of the method so as to return another object where the receiver is referred to as a field in that object?

Edit / Update

Thanks to @cafce25 for his reminder on giving fully reproducible codes and the error from the compiler.

Here is it:


use pyo3::{
    prelude::{*, Py, PyAny},
};

#[pymodule]
fn stackoverflowqn(py: Python, pymod: &PyModule) -> PyResult<()> {
    #[pyclass(name = "class_a")]
    #[derive(Clone, Debug)]
    pub struct ClassA {
        pv: Option<Py<PyAny>>, // private value
        field_a: Py<PyAny>,
    }

    #[pyclass]
    #[derive(Clone, Debug)]

    pub struct ClassB {
        class_a: Py<ClassA>,
        field_b: Py<PyAny>,
    }

    #[pymethods]
    impl ClassA {
        #[new]
        pub fn __new__(_slf: PyRef<'_, Self>, field_a: PyObject) -> PyResult<ClassA> {
            Ok(ClassA {
                pv: None,
                field_a: field_a,
            })
        }

        fn ret_class_b {&self, field_b: &PyAny) -> PyResult<ClassB> {
            let class_a: Py<ClassA> = Py::clone_ref(self, field_b.py());
            Ok(ClassB {
                class_a: class_a,
                field_b: field_b.into_py(),
            })
        }
    }
}

Here is the compiler error:

error[E0277]: the trait bound `ClassA: pyo3::IntoPy<pyo3::Py<ClassA>>` is not satisfied
  --> crates\cached-property\src\stackoverflow.rs:36:53
   |
36 |                 pyo3::IntoPy::<Py<ClassA>>::into_py(pty_getter, py);
   |                 ----------------------------------- ^^^^^^^^^^ the trait `pyo3::IntoPy<pyo3::Py<ClassA>>` is not implemented for `ClassA`
   |                 |
   |                 required by a bound introduced by this call
   |
   = help: the trait `pyo3::IntoPy<pyo3::Py<PyAny>>` is implemented for `ClassA`

Solution

  • The issue is how do I get &Py<ClassA> from the receiver of the method so as to return another object where the receiver is referred to as a field in that object?

    If I understood correctly your problem is constructing Py<ClassA> from &ClassA available in your method through &self. I think what you are looking for is a change in your method signature. There is little documentation on this in the user guide, but if you look at the examples in PyRef you see that you can pass an instance of PyRef<'_, Self> into your method instead of &self:

    You can use PyRef as an alternative to a &self receiver

    You can convert PyRef into a Py pointer simply by calling .into().

    I've made some changes to your example and this compiles on my computer:

    #[pyclass]
    struct ClassA {
        pv: Py<PyAny>, // GIL independent type, storable in another #[pyclass]
        field_a: Py<PyAny>,
    }
    
    #[pymethods]
    impl ClassA {
        fn ret_class_b(slf: PyRef<'_, Self>, field_b: Py<PyAny>) -> PyResult<ClassB> {
            Ok(ClassB {
                class_a: slf.into(),
                field_b,
            })
        }
    }
    
    #[pyclass]
    struct ClassB {
        class_a: Py<ClassA>,
        field_b: Py<PyAny>,
    }