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

pyfn: deprecate name argument #1610

Merged
merged 1 commit into from
May 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `PyAsyncProtocol::__aenter__` and `PyAsyncProtocol::__aexit__`
- Deprecate `#[name = "..."]` attributes in favor of `#[pyo3(name = "...")]`. [#1567](https://github.com/PyO3/pyo3/pull/1567)
- Improve compilation times for projects using PyO3 [#1604](https://github.com/PyO3/pyo3/pull/1604)
- Deprecate string-literal second argument to `#[pyfn(m, "name")]`.

### Removed
- Remove deprecated exception names `BaseException` etc. [#1426](https://github.com/PyO3/pyo3/pull/1426)
Expand Down
2 changes: 0 additions & 2 deletions guide/src/class/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,6 @@ These correspond to the slots `tp_traverse` and `tp_clear` in the Python C API.
as every cycle must contain at least one mutable reference.
Example:
```rust
extern crate pyo3;

use pyo3::prelude::*;
use pyo3::PyTraverseError;
use pyo3::gc::{PyGCProtocol, PyVisit};
Expand Down
106 changes: 64 additions & 42 deletions guide/src/function.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
# Python Functions

PyO3 supports two ways to define a free function in Python. Both require registering
the function to a [module](./module.md).
PyO3 supports two ways to define a free function in Python. Both require registering the function to a [module](./module.md).

One way is defining the function in the module definition, annotated with `#[pyfn]`.
One way is annotating a function with `#[pyfunction]` and then adding it to the module using the `wrap_pyfunction!` macro.

```rust
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}

#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?)?;
Ok(())
}

# fn main() {}
```

Alternatively there is a shorthand; the function can be placed inside the module definition and annotated with `#[pyfn]`, as below:

```rust
use pyo3::prelude::*;

#[pymodule]
fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {

#[pyfn(m, "sum_as_string")]
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
#[pyfn(m)]
fn sum_as_string(_py: Python, a:i64, b:i64) -> PyResult<String> {
Ok(format!("{}", a + b))
}

Expand All @@ -22,38 +41,61 @@ fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {
# fn main() {}
```

The other is annotating a function with `#[pyfunction]` and then adding it
to the module using the `wrap_pyfunction!` macro.
`#[pyfn(m)]` is just syntax sugar for `#[pyfunction]`, and takes all the same options documented in the rest of this chapter. The code above is expanded to the following:

```rust
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}

#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(double, m)?).unwrap();
fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {

#[pyfunction]
fn sum_as_string(_py: Python, a:i64, b:i64) -> PyResult<String> {
Ok(format!("{}", a + b))
}

m.add_function(pyo3::wrap_pyfunction!(sum_as_string, m)?)?;

Ok(())
}

# fn main() {}
```

## Function options

The `#[pyo3]` attribute can be used to modify properties of the generated Python function. It can take any combination of the following options:

- `#[pyo3(name = "...")]`

Overrides the name generated in Python:

```rust
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;

#[pyfunction]
#[pyo3(name = "no_args")]
fn no_args_py() -> usize { 42 }

#[pymodule]
fn module_with_functions(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(no_args_py, m)?)?;
Ok(())
}

# Python::with_gil(|py| {
# let m = pyo3::wrap_pymodule!(module_with_functions)(py);
# assert!(m.getattr(py, "no_args").is_ok());
# assert!(m.getattr(py, "no_args_py").is_err());
# });
```

## Argument parsing

Both the `#[pyfunction]` and `#[pyfn]` attributes support specifying details of
argument parsing. The details are given in the section "Method arguments" in
the [Classes](class.md) chapter. Here is an example for a function that accepts
arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number
that was passed:
The `#[pyfunction]` attribute supports specifying details of argument parsing. The details are given in the section ["Method arguments" of the Classes chapter](class.md#method-arguments). Here is an example for a function that accepts arbitrary keyword arguments (`**kwargs` in Python syntax) and returns the number that was passed:

```rust
# extern crate pyo3;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use pyo3::types::PyDict;
Expand Down Expand Up @@ -213,8 +255,7 @@ in Python code.

### Accessing the module of a function

It is possible to access the module of a `#[pyfunction]` and `#[pyfn]` in the
function body by passing the `pass_module` argument to the attribute:
It is possible to access the module of a `#[pyfunction]` in the function body by passing the `pass_module` argument to the attribute:

```rust
use pyo3::wrap_pyfunction;
Expand All @@ -236,25 +277,6 @@ fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {
If `pass_module` is set, the first argument **must** be the `&PyModule`. It is then possible to use the module
in the function body.

The same works for `#[pyfn]`:

```rust
use pyo3::wrap_pyfunction;
use pyo3::prelude::*;

#[pymodule]
fn module_with_fn(py: Python, m: &PyModule) -> PyResult<()> {

#[pyfn(m, "module_name", pass_module)]
fn module_name(module: &PyModule) -> PyResult<&str> {
module.name()
}
Ok(())
}

# fn main() {}
```

## Accessing the FFI functions

In order to make Rust functions callable from Python, PyO3 generates a
Expand Down
9 changes: 5 additions & 4 deletions guide/src/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ fn rust2py(py: Python, m: &PyModule) -> PyResult<()> {
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
// Python objects to Rust values, and the Rust return value back into a Python object.
// The `_py` argument represents that we're holding the GIL.
#[pyfn(m, "sum_as_string")]
#[pyfn(m)]
#[pyo3(name = "sum_as_string")]
fn sum_as_string_py(_py: Python, a: i64, b: i64) -> PyResult<String> {
let out = sum_as_string(a, b);
Ok(out)
Expand All @@ -32,7 +33,7 @@ fn sum_as_string(a: i64, b: i64) -> String {
# fn main() {}
```

The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your
The `#[pymodule]` procedural macro attribute takes care of exporting the initialization function of your
module to Python. It can take as an argument the name of your module, which must be the name of the `.so`
or `.pyd` file; the default is the Rust function's name.

Expand All @@ -41,7 +42,7 @@ If the name of the module (the default being the function name) does not match t
`ImportError: dynamic module does not define module export function (PyInit_name_of_your_module)`

To import the module, either copy the shared library as described in [the README](https://github.com/PyO3/pyo3)
or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
or use a tool, e.g. `maturin develop` with [maturin](https://github.com/PyO3/maturin) or
`python setup.py develop` with [setuptools-rust](https://github.com/PyO3/setuptools-rust).

## Documentation
Expand Down Expand Up @@ -95,4 +96,4 @@ fn supermodule(py: Python, module: &PyModule) -> PyResult<()> {

This way, you can create a module hierarchy within a single extension module.

It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module.
It is not necessary to add `#[pymodule]` on nested modules, this is only required on the top-level module.
13 changes: 8 additions & 5 deletions pyo3-macros-backend/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use syn::{
Attribute, ExprPath, Ident, LitStr, Result, Token,
};

use crate::deprecations::{Deprecation, Deprecations};

pub mod kw {
syn::custom_keyword!(annotation);
syn::custom_keyword!(attribute);
Expand Down Expand Up @@ -81,17 +83,18 @@ pub fn take_attributes(
Ok(())
}

pub fn get_deprecated_name_attribute(attr: &syn::Attribute) -> syn::Result<Option<NameAttribute>> {
pub fn get_deprecated_name_attribute(
attr: &syn::Attribute,
deprecations: &mut Deprecations,
) -> syn::Result<Option<NameAttribute>> {
match attr.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(s),
..
})) if path.is_ident("name") => {
let mut ident: syn::Ident = s.parse()?;
// This span is the whole attribute span, which is nicer for reporting errors.
ident.set_span(attr.span());
Ok(Some(NameAttribute(ident)))
deprecations.push(Deprecation::NameAttribute, attr.span());
Ok(Some(NameAttribute(s.parse()?)))
}
_ => Ok(None),
}
Expand Down
43 changes: 43 additions & 0 deletions pyo3-macros-backend/src/deprecations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use proc_macro2::{Span, TokenStream};
use quote::{quote_spanned, ToTokens};

pub enum Deprecation {
NameAttribute,
PyfnNameArgument,
}

impl Deprecation {
fn ident(&self, span: Span) -> syn::Ident {
let string = match self {
Deprecation::NameAttribute => "NAME_ATTRIBUTE",
Deprecation::PyfnNameArgument => "PYFN_NAME_ARGUMENT",
};
syn::Ident::new(string, span)
}
}

#[derive(Default)]
pub struct Deprecations(Vec<(Deprecation, Span)>);

impl Deprecations {
pub fn new() -> Self {
Deprecations(Vec::new())
}

pub fn push(&mut self, deprecation: Deprecation, span: Span) {
self.0.push((deprecation, span))
}
}

impl ToTokens for Deprecations {
fn to_tokens(&self, tokens: &mut TokenStream) {
for (deprecation, span) in &self.0 {
let ident = deprecation.ident(*span);
quote_spanned!(
*span =>
let _ = pyo3::impl_::deprecations::#ident;
)
.to_tokens(tokens)
}
}
}
25 changes: 13 additions & 12 deletions pyo3-macros-backend/src/konst.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::attributes::{
self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident, take_attributes,
NameAttribute,
use crate::{
attributes::{
self, get_deprecated_name_attribute, get_pyo3_attributes, is_attribute_ident,
take_attributes, NameAttribute,
},
deprecations::Deprecations,
};
use crate::utils;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
Expand All @@ -19,12 +21,10 @@ pub struct ConstSpec {

impl ConstSpec {
/// Null-terminated Python name
pub fn python_name_with_deprecation(&self) -> TokenStream {
pub fn null_terminated_python_name(&self) -> TokenStream {
if let Some(name) = &self.attributes.name {
let deprecation =
utils::name_deprecation_token(name.0.span(), self.attributes.name_is_deprecated);
let name = format!("{}\0", name.0);
quote!({#deprecation #name})
quote!({#name})
} else {
let name = format!("{}\0", self.rust_ident.unraw().to_string());
quote!(#name)
Expand All @@ -35,7 +35,7 @@ impl ConstSpec {
pub struct ConstAttributes {
pub is_class_attr: bool,
pub name: Option<NameAttribute>,
pub name_is_deprecated: bool,
pub deprecations: Deprecations,
}

pub enum PyO3ConstAttribute {
Expand All @@ -58,7 +58,7 @@ impl ConstAttributes {
let mut attributes = ConstAttributes {
is_class_attr: false,
name: None,
name_is_deprecated: false,
deprecations: Deprecations::new(),
};

take_attributes(attrs, |attr| {
Expand All @@ -76,9 +76,10 @@ impl ConstAttributes {
}
}
Ok(true)
} else if let Some(name) = get_deprecated_name_attribute(attr)? {
} else if let Some(name) =
get_deprecated_name_attribute(attr, &mut attributes.deprecations)?
{
attributes.set_name(name)?;
attributes.name_is_deprecated = true;
Ok(true)
} else {
Ok(false)
Expand Down
1 change: 1 addition & 0 deletions pyo3-macros-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod utils;

mod attributes;
mod defs;
mod deprecations;
mod from_pyobject;
mod konst;
mod method;
Expand Down
Loading