update #[derive(FromPyObject)] to use extract_bound#3828
update #[derive(FromPyObject)] to use extract_bound#3828davidhewitt merged 2 commits intoPyO3:mainfrom
#[derive(FromPyObject)] to use extract_bound#3828Conversation
I think we want to abstract over the return value and are in macro context? So we could use autoref-based specialization like we do for iterator return values and other things? But admittedly I am not a fan of this and would personally prefer option 2. |
|
We might be able to get away without specialization and just use some type inference to wrap the closure at the points where we need it. I was playing with a similar trick for |
I think we would need to abstract over the whole (user provided) closure type, no?
I tried something like this, but wrapping closures is always a bit weird, because we can't name them. I also ran into problems with overlapping trait implementations for the conversions. (In principle there could be a single type which implements |
|
With function pointers we could do something like this: pub enum Extractor<'a, 'py, T> {
Bound(fn(&'a Bound<'py, PyAny>) -> PyResult<T>),
GilRef(fn(&'a PyAny) -> PyResult<T>),
}
impl<'a, 'py, T> From<fn(&'a Bound<'py, PyAny>) -> PyResult<T>> for Extractor<'a, 'py, T> {
fn from(value: fn(&'a Bound<'py, PyAny>) -> PyResult<T>) -> Self {
Self::Bound(value)
}
}
impl<'a, T> From<fn(&'a PyAny) -> PyResult<T>> for Extractor<'a, '_, T> {
fn from(value: fn(&'a PyAny) -> PyResult<T>) -> Self {
Self::GilRef(value)
}
}
impl<'a, 'py, T> Extractor<'a, 'py, T> {
fn call(self, obj: &'a Bound<'py, PyAny>) -> PyResult<T> {
match self {
Extractor::Bound(f) => f(obj),
Extractor::GilRef(f) => f(obj.as_gil_ref()),
}
}
}And since |
CodSpeed Performance ReportMerging #3828 will degrade performances by 15.02%Comparing Summary
Benchmarks breakdown
|
davidhewitt
left a comment
There was a problem hiding this comment.
Ah, nicely solved!
And since
from_py_withhas to be a ExprPath I think all uses have to real functions (or methods) anyway.
I agree with this thinking too.
I think this implementation will probably lead to some painful error messages for users if they get the type of the from_py_with function wrong. I suspect that it'd be hard to do much better and we can remove the Extractor wrapper in the future to get better type hints in errors again 👍
In this PR I've converted the
FromPyObjectproc-macro to output the newextract_boundfunction. Most of this should be completely invisible to downstream, but one place where this leaks out is the#[pyo3(from_py_with = ...)]attribute.So leaving this as is, would be a breaking change. I don't really see a good way of abstracting over two function types with different signatures.
I can see two options to avoid the breakage:
#[pyo3(from_py_with_bound = ...)]attributeProbably option 2 is preferable, so that we can provide an upgrade path. But maybe there is another option which I don't see right now... 🙃