Skip to content
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

Pymodule bound #3897

Merged
merged 8 commits into from
Feb 27, 2024
Merged

Pymodule bound #3897

merged 8 commits into from
Feb 27, 2024

Conversation

LilyFoote
Copy link
Contributor

Builds upon #3744.

@davidhewitt davidhewitt added the CI-skip-changelog Skip checking changelog entry label Feb 25, 2024
Co-authored-by: David Hewitt <mail@davidhewitt.dev>
@LilyFoote LilyFoote marked this pull request as ready for review February 25, 2024 13:29
Copy link
Contributor

@Icxolu Icxolu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to see this getting closer to landing. After this I think we can really start to update documentation and examples! 🚀

I have added some thoughts, but you should definitely wait for a second opinion.

src/derive_utils.rs Outdated Show resolved Hide resolved
@@ -126,13 +126,42 @@ macro_rules! py_run_impl {
macro_rules! wrap_pyfunction {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should now get a deprecation warning I thing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot! I think maybe this makes sense to do in a separate PR since it'll cause a lot of churn in tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like that is #3899

src/types/module.rs Outdated Show resolved Hide resolved
src/macros.rs Outdated Show resolved Hide resolved
Co-authored-by: Matthew Neeley <mneeley@gmail.com>
src/impl_/pymodule.rs Outdated Show resolved Hide resolved
src/types/module.rs Outdated Show resolved Hide resolved
This allows us to remove the awkward `PyFunctionArgumentsBound` enum.
src/macros.rs Outdated Show resolved Hide resolved
@davidhewitt
Copy link
Member

I pushed a final commit which removes the #[doc_hidden] fn wrap_pyfunction and enables type inference for wrap_pyfunction_bound again. Please forgive me @LilyFoote, I don't normally do that uninvited, I just thought that it might be helpful in this case as this PR seems to be one of the critical bits remaining before we can release 0.21.

I couldn't find a good way to describe what I was thinking until I'd tried it out, at which point I had the commit ready to go 🙈

Do let me know what you think of that last commit, if it seems good, maybe this is ready to merge?!

@davidhewitt
Copy link
Member

Ok so I had a slightly nutty magical idea to use some trickery to automatically infer the output type of wrap_pyfunction from the input. It now continues to work as-is, but now also accepts Bound<'py, Module> (and friends) and will return a Bound<'py, PyCFunction> in those cases.

If the input argument is Python<'py> then we can't use inference and instead users have to use wrap_pyfunction_bound! just in that case.

The nice thing about this is that then for the vast majority of user code they don't need to change anything in their #[pymodule] other than change from &'py PyModule to &Bound<'py, PyModule> at the top and everything else keeps working. We wouldn't even need to deprecate wrap_pyfunction then!

I pushed that as a final commit. What do you think?

Copy link

codspeed-hq bot commented Feb 27, 2024

CodSpeed Performance Report

Merging #3897 will improve performances by 11.35%

Comparing LilyFoote:pymodule-bound (f7a4e82) with main (404161c)

Summary

⚡ 1 improvements
✅ 66 untouched benchmarks

Benchmarks breakdown

Benchmark main LilyFoote:pymodule-bound Change
extract_str_extract_success 1,090 ns 978.9 ns +11.35%


pub fn add_to_module(module: &#krate::Bound<'_, #krate::types::PyModule>) -> #krate::PyResult<()> {
use #krate::prelude::PyModuleMethods;
use ::std::convert::Into;
module.add_function(&#krate::types::PyCFunction::internal_new(&DEF, module.as_gil_ref().into())?)
module.add_function(#krate::types::PyCFunction::internal_new(&DEF, module.as_gil_ref().into())?)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit sad to still need this as_gil_ref() call for now.

@LilyFoote
Copy link
Contributor Author

Looks good to me! If @Icxolu and @maffoo are happy too, I think this is ready!

Copy link
Contributor

@maffoo maffoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I think it'd be good to move forward with this and we can revisit the macro import hygiene in a follow-up.

let krate = get_pyo3_crate(&options.krate);

let mut stmts: Vec<syn::Stmt> = vec![syn::parse_quote!(
#[allow(unknown_lints, unused_imports, redundant_imports)]
use #krate::{PyNativeType, types::PyModuleMethods};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hesitant to add this kind of use statement, which transparently injects things into the namespace of user code and so would seem to violate hygiene (see discussion in the follow-up PR here: #3899 (comment)). An alternative would be to use fully-qualified references to trait methods in the macro-generated code. But as @Icxolu noted in that discussion, the patterns around these new *Methods traits are not very well established, so I could be persuaded :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, after that discussion I tend to agree, that we should not inject these traits silently. If we still want to do it we should probably at least import them anonymously e.g. as use "Trait" as _. I would not consider it a blocker here, we can change in a followup if needed. Another thing would be if we should seal these traits, but that's another discussion entirely.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Maybe a good solution here it to write a private trait in impl_/pymodule.rs which has just the methods we need implemented for both Bound<'_, PyModule> and &'py PyModule, which we can then call directly without imports.

(This is actually probably very similar to what @LilyFoote had already done with the #[doc(hidden)] fn wrap_pyfunction, just located as a private trait inside the impl codebase.)

Copy link
Contributor

@Icxolu Icxolu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice solution, this look pretty elegant. I have found a minor point regarding PyFunction::internal_new_bound plus two general comments, otherwise LGTM!

@@ -189,6 +206,35 @@ impl PyCFunction {
.downcast_into_unchecked()
}
}

#[doc(hidden)]
pub(crate) fn internal_new_bound<'py>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need this. We can change the signature of internal_new to match the new _bound APIs and move let (py, module) = py_or_module.into_py_and_maybe_module(); into the deprecated gil-ref APIs

@@ -590,7 +591,7 @@ pub trait PyModuleMethods<'py> {
///
/// [1]: crate::prelude::pyfunction
/// [2]: crate::wrap_pyfunction
fn add_function(&self, fun: &Bound<'_, PyCFunction>) -> PyResult<()>;
fn add_function(&self, fun: Bound<'_, PyCFunction>) -> PyResult<()>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is just to make it nicer, so that we don't have to use&wrap_pyfunction! instead of just wrap_pyfunction!?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it's probably worth making this change? It's not like we expect this to be called in a hot loop. I guess we could also have impl AsRef<Bound<'_, PyCFunction>>?

Comment on lines +10 to +12
pub trait WrapPyFunctionArg<'py, T> {
fn wrap_pyfunction(self, method_def: &PyMethodDef) -> PyResult<T>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very interesting solution! I guess T could also be an associated type given that it's not intended to be implemented more than once per type. But since this is internal API, I'm fine either way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes actually I was thinking that later on today, I had generic originally because I wanted to play with type inference but given that each type now implements only once we could go for the associated type.

let krate = get_pyo3_crate(&options.krate);

let mut stmts: Vec<syn::Stmt> = vec![syn::parse_quote!(
#[allow(unknown_lints, unused_imports, redundant_imports)]
use #krate::{PyNativeType, types::PyModuleMethods};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, after that discussion I tend to agree, that we should not inject these traits silently. If we still want to do it we should probably at least import them anonymously e.g. as use "Trait" as _. I would not consider it a blocker here, we can change in a followup if needed. Another thing would be if we should seal these traits, but that's another discussion entirely.

@davidhewitt
Copy link
Member

Thanks all! I think given this PR seems to be blocking a lot of the final refinements to the codebase which we need for 0.21, and we're happy enough with this as-is, I think let's merge this as-is. There are quite a few follow-up items. Anyone got time / interest in taking them on? Otherwise I can try to do these maybe at the weekend.

(I also just made some comments on other issues specifically about the trait sealing...)

@davidhewitt davidhewitt added this pull request to the merge queue Feb 27, 2024
Merged via the queue into PyO3:main with commit a3ad28b Feb 27, 2024
41 of 42 checks passed
@LilyFoote LilyFoote deleted the pymodule-bound branch February 27, 2024 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI-skip-changelog Skip checking changelog entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants