-
I am creating a Python API for a Rust library. Some of the methods return nested Rust structs. For example:
I would like the Python code to be able to access all members of the returned structs. The simplest option might be to define To avoid this performance hit, we need to fully convert the struct into a Python compatible object. I can think of two different ways this could be achieved.
Probably this could even be generated automatically by a macro.
Does this seem like a reasonable approach? |
Beta Was this translation helpful? Give feedback.
Replies: 8 comments
-
Option 1 is probably what I would pick for now. Note that in the future I would like it to be possible for This is in reality still some way off. |
Beta Was this translation helpful? Give feedback.
-
We should probably add a section in the guide that discusses this stuff in depth.
You're going to have to take that hit somewhere on the Rust/Python boundary. You could allocate everything on the Python heap (so, you'd have
Also, it would return a fresh clone on every access, so Python code wouldn't be able to mutate the collection. See https://pyo3.rs/main/faq.html#pyo3get-clones-my-field Here's a third approach: #[pyclass]
struct Foo{
bar: Py<Bar>
}
#[pyclass]
struct Bar{
inner: HashMap<Py<ContainerId>, Py<ContainerStatus>>
}
#[pyproto]
impl PyMappingProtocol for Bar {
/* todo */
}
#[pyproto]
impl PyIterProtocol for Bar {
/* todo */
}
#[pyclass]
struct BarIter{
inner: Py<Bar>,
state: /* todo */
}
#[pyproto]
impl PyIterProtocol for BarIter {
/* todo */
} What is best will depend on what exactly you are doing (and benchmarks, probably). YMMV. |
Beta Was this translation helpful? Give feedback.
-
Related question, how do you create something like a |
Beta Was this translation helpful? Give feedback.
-
You can use use pyo3::prelude::*;
use pyo3::types::PyDict;
struct Bar {
inner: Py<PyDict>,
}
impl Bar {
fn new() -> Bar {
Python::with_gil(|py| {
let dict: Py<PyDict> = PyDict::new(py).into();
Bar {
inner: dict,
}
})
}
} You can use |
Beta Was this translation helpful? Give feedback.
-
I tried option 1 and it's a fine solution, but ended up going with option 2 instead where I just define the classes in Python and import them and convert on the Rust side. I'm quite happy with this, it's roughly the same amount of effort/boilerplate to creating Python-compatible structs in Rust but I also get MyPy type annotations and all the |
Beta Was this translation helpful? Give feedback.
-
Just a question. Performance wise, how does doing this: "Create a second version for each struct in Rust which is compatible in Python and then manually convert these from Rust code." compared to using a convenient crate called |
Beta Was this translation helpful? Give feedback.
-
My gut feeling is that you can get better performance from doing hand-written code over using |
Beta Was this translation helpful? Give feedback.
-
My own experimentation seems to show little difference between using pythonize and hand-coding to_python method. Didn’t do any crazy optimisation though. I guess most of the time is spent on instantiating and filling up PyList and PyDict and such. pythonize does not seem to have so much overhead. |
Beta Was this translation helpful? Give feedback.
We should probably add a section in the guide that discusses this stuff in depth.
You're going to have to take that hit somewhere on the Rust/Python boundary. You could allocate everything on the Python heap (so, you'd have
Py<...>
wrappers everywhere) which avoids the cloning but this just moves some cost to conversions when Rust code needs to work on the structs.Also, it would return a …