-
Notifications
You must be signed in to change notification settings - Fork 829
Use single-arg form of #[pymodule]
function in docs and tests
#3899
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
aae06e9
to
a0a71d8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
I've added some thoughts below.
src/macros.rs
Outdated
@@ -123,16 +123,46 @@ macro_rules! py_run_impl { | |||
/// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
tests/test_anyhow.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might not actually need this bit, because it's part of the GIL Ref refactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can remove the fragment here, and then add one with the notice about the #[pymodule]
change in that new PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on! There are still some open questions in #3897, so it might take a few days until that lands. In the meanwhile I've added some comments/thoughts below.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 use
ing the trait.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.as_borrowed
should not be necessary anymore, because this is already a Bound
now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, this should be generated by the macro
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's much nicer!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! The modules feel very clean now 🎉
Co-authored-by: Icxolu <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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!
tests/test_module.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test with a two-argument module function.
CodSpeed Performance ReportMerging #3899 will improve performances by 12.48%Comparing Summary
Benchmarks breakdown
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks again 👍
* Use single-arg form for `#[pymodule]` functions in docs and tests * Update guide/src/function.md Co-authored-by: Icxolu <[email protected]> * Add test of two-argument module function * Fix new test --------- Co-authored-by: Icxolu <[email protected]>
As a follow-up to #3897, this deprecates the
wrap_pyfunction
macro and recommends replacing it withwrap_pyfunction_bound
. As an experiment, I have also modified thepymodule
macro 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_function
are not available unless we also import thePyModuleMethods
trait, so I've added an explicituse pyo3::types::PyModuleMethods
in 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.