Repository navigation
|
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? |
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. |
|
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. |
|
Related question, how do you create something like a |
|
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 |
|
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 |
|
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 |
|
My gut feeling is that you can get better performance from doing hand-written code over using |
|
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. |
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 …