Use single-arg form of #[pymodule] function in docs and tests#3899
Use single-arg form of #[pymodule] function in docs and tests#3899davidhewitt merged 4 commits intoPyO3:mainfrom
#[pymodule] function in docs and tests#3899Conversation
aae06e9 to
a0a71d8
Compare
LilyFirefly
left a comment
There was a problem hiding this comment.
Thanks for doing this!
I've added some thoughts below.
src/macros.rs
Outdated
| /// This can be used with [`PyModule::add_function`](crate::types::PyModule::add_function) to add free | ||
| /// functions to a [`PyModule`](crate::types::PyModule) - see its documentation for more information. | ||
| #[macro_export] | ||
| #[deprecated(since = "0.21.0", note = "Use `wrap_pyfunction_bound` instead.")] |
There was a problem hiding this comment.
Perhaps we should include ! to make it clear we mean a macro?
| #[deprecated(since = "0.21.0", note = "Use `wrap_pyfunction_bound` instead.")] | |
| #[deprecated(since = "0.21.0", note = "Use `wrap_pyfunction_bound!` instead.")] |
tests/test_anyhow.rs
Outdated
|
|
||
| Python::with_gil(|py| { | ||
| let func = wrap_pyfunction!(produce_ok_result)(py).unwrap(); | ||
| let func = wrap_pyfunction_bound!(produce_ok_result, py).unwrap(); |
There was a problem hiding this comment.
Is there a reason you changed the calling style here? For consistency across the codebase?
It might be valuable to test both styles somewhere. (Maybe we already do?)
There was a problem hiding this comment.
Yeah, I'd be happy to change back if desired; I didn't notice whether there are explicit tests of both styles somewhere. TBH I didn't realize the single-arg call to make a closure was even a thing, but in all these cases where the closure is invoked immediately it seems like one call with two args is more clear.
pyo3-macros-backend/src/module.rs
Outdated
| #vis mod #ident { | ||
| #initialization | ||
| } | ||
| if function.sig.inputs.len() == 1 { |
There was a problem hiding this comment.
I think I like what this allows us to do, but I don't like the duplication here in the macro. I wonder if there's a way to get the best of both worlds?
In the future I think it would be helpful to add new functionality like this in a separate PR. That way it's easier to separate the changes we already know we want from those that we need to consider.
There was a problem hiding this comment.
I agree this is a nice addition, but we should split this off as a separate PR. The changes to wrap_pyfunction(_bound) already have quite a big diff. (And since this is already in a separate commit, it should be easy to split off).
As for the implementation, maybe we can do something similar to #[pyfunction] which can already do this. I think this is the relevant code for that. This should avoid a lot of this duplication
There was a problem hiding this comment.
Thanks for the link to the pyfunction code. I will take a look at that and split off the macro changes as a separate PR. I'd prefer to land that one first so that the wrap_pyfunction_bound changes can use the single-arg form for pymodule functions. WDYT?
There was a problem hiding this comment.
I'd be fine with that. The changes are only tangentially related, so the order they land in does not matter to much I think.
newsfragments/3899.changed.md
Outdated
| @@ -0,0 +1,2 @@ | |||
| The `wrap_pyfunction!` macro is now deprecated. The `wrap_pyfunction_bound!` macro, which returns a `Bound<'_, PyCFunction>` should be used instead. | |||
There was a problem hiding this comment.
We might not actually need this bit, because it's part of the GIL Ref refactor.
There was a problem hiding this comment.
Yeah, we skipped all the other gil-ref refactorings from the changelog, so I don't think we should start that here now.
The change to the #[pymodule] macro should be mentioned, but as in the other comment, I think we should land that separately.
There was a problem hiding this comment.
I will split off the pymodule macro changes, but I'm not exactly sure what your suggestion is for this news fragment. Are you suggesting I keep the notice about deprecating wrap_pyfunction! but omit the reference to Bound, or should I not add a newsfragment at all?
There was a problem hiding this comment.
I think you can remove the fragment here, and then add one with the notice about the #[pymodule] change in that new PR.
src/tests/hygiene/pymodule.rs
Outdated
| #[crate::pymodule] | ||
| #[pyo3(crate = "crate")] | ||
| fn my_module_bound(_py: crate::Python<'_>, m: &crate::Bound<'_, crate::types::PyModule>) -> crate::PyResult<()> { | ||
| use crate::types::PyModuleMethods; |
There was a problem hiding this comment.
I believe the point of this test is, to verify that this works without additional imports. So the macro code needs to be adapted that this is also generated.
There was a problem hiding this comment.
Yeah, I feel like the hygiene and no-import tests are a bit at odds in this regard, because having things work without extra imports means that the macro would need to introduce a hidden import of the PyModuleMethods trait into the namespace, which feels unhygienic. IMO it makes sense that if the user wants to call m.add_function on a Bound<PyMethod> they should have to explicitly import PyModuleMethods, just like they would have to do if making this call without a macro. I noticed that in test_no_imports.rs we were already pulling in PyAnyMethods, presumably for the same reasons, so I've added PyModuleMethods there. For the hygiene tests, I've changed to using the syntax for qualified trait methods, instead of useing the trait.
There was a problem hiding this comment.
Yeah, I can see your point here. These traits are all new, so I guess we need to figure out how we want to handle them.
I guess the main point to test here is that the macro expansion compiles without additional imports. So fully qualified syntax seems appropriate here.
Good catch with PyAnyMethods in test_no_imports.rs seems like I added that 😇, shame on me 😆. I think that's only needed for test_basic, so maybe we should move that local instead of moving PyModuleMethods global? That way we can maybe at least somewhat verify that macro expansion does not rely on those imports, while still keeping this close to what a user might write.
After writing this, I think I agree that we should not generate that import silently.
There was a problem hiding this comment.
I think most code won't actually need to explicitly add PyAnyMethods because it's in the prelude.
tests/test_coroutine.rs
Outdated
| wrap_pyfunction!(my_fn, gil).unwrap().as_borrowed().as_any(), | ||
| wrap_pyfunction_bound!(my_fn, gil) | ||
| .unwrap() | ||
| .as_borrowed() |
There was a problem hiding this comment.
.as_borrowed should not be necessary anymore, because this is already a Bound now
tests/test_no_imports.rs
Outdated
|
|
||
| #[pyo3::pymodule] | ||
| fn basic_module_bound(_py: pyo3::Python<'_>, m: &pyo3::Bound<'_, pyo3::types::PyModule>) -> pyo3::PyResult<()> { | ||
| use pyo3::types::PyModuleMethods; |
There was a problem hiding this comment.
Same here, this should be generated by the macro
There was a problem hiding this comment.
See comment above about hygiene test.
pyo3-macros-backend/src/module.rs
Outdated
| #vis mod #ident { | ||
| #initialization | ||
| } | ||
| if function.sig.inputs.len() == 1 { |
There was a problem hiding this comment.
I agree this is a nice addition, but we should split this off as a separate PR. The changes to wrap_pyfunction(_bound) already have quite a big diff. (And since this is already in a separate commit, it should be easy to split off).
As for the implementation, maybe we can do something similar to #[pyfunction] which can already do this. I think this is the relevant code for that. This should avoid a lot of this duplication
newsfragments/3899.changed.md
Outdated
| @@ -0,0 +1,2 @@ | |||
| The `wrap_pyfunction!` macro is now deprecated. The `wrap_pyfunction_bound!` macro, which returns a `Bound<'_, PyCFunction>` should be used instead. | |||
There was a problem hiding this comment.
Yeah, we skipped all the other gil-ref refactorings from the changelog, so I don't think we should start that here now.
The change to the #[pymodule] macro should be mentioned, but as in the other comment, I think we should land that separately.
9c62e38 to
1486fe6
Compare
wrap_pyfunction macro in favor of wrap_pyfunction_bound#[pymodule] function in docs and tests
|
Now that #3897 and #3905 have been merged, I've reworked this to not deprecated |
|
Thanks, I will aim to review this tonight or tomorrow 👍 |
Icxolu
left a comment
There was a problem hiding this comment.
Very nice! The modules feel very clean now 🎉
Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
davidhewitt
left a comment
There was a problem hiding this comment.
Agreed, looks brilliant, thank you!
I'd like to make sure we continue to test the old form so that we don't end up with all the existing code being broken accidentally, other thank that, looks great to merge!
There was a problem hiding this comment.
Let's perhaps have one test in this file with the two-argument form, just so that we can be sure that continues to work (even if we're not encouraging new usage of it).
There was a problem hiding this comment.
Added a test with a two-argument module function.
CodSpeed Performance ReportMerging #3899 will improve performances by 12.48%Comparing Summary
Benchmarks breakdown
|
davidhewitt
left a comment
There was a problem hiding this comment.
Looks great! Thanks again 👍
* Use single-arg form for `#[pymodule]` functions in docs and tests * Update guide/src/function.md Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com> * Add test of two-argument module function * Fix new test --------- Co-authored-by: Icxolu <10486322+Icxolu@users.noreply.github.com>
As a follow-up to #3897, this deprecates the
wrap_pyfunctionmacro and recommends replacing it withwrap_pyfunction_bound. As an experiment, I have also modified thepymodulemacro to allow a single-argument form so that module functions can take just a singleBound<'_, PyModule>argument, since one can obtain aPython<'_>marker from the bound reference if needed. While this works, the macro implementation itself can probably be improved.One question I have is around the macro hygiene and "no import" tests; when using
&Bound<PyModule>instead of&PyModule, methods such asm.add_functionare not available unless we also import thePyModuleMethodstrait, so I've added an explicituse pyo3::types::PyModuleMethodsin both those tests. For the "no import" test in particular this looks a little funny, so I'd be happy to know if there's a better approach.