diff --git a/guide/src/function.md b/guide/src/function.md index b0d6c0b0e98..fe74e5df457 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -73,39 +73,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python - `#[pyo3(text_signature = "...")]` - Sets the function signature visible in Python tooling (such as via [`inspect.signature`]). - - The example below creates a function `add` which has a signature describing two positional-only - arguments `a` and `b`. - - ```rust - use pyo3::prelude::*; - - /// This function adds two unsigned 64-bit integers. - #[pyfunction] - #[pyo3(text_signature = "(a, b, /)")] - fn add(a: u64, b: u64) -> u64 { - a + b - } - # - # fn main() -> PyResult<()> { - # Python::with_gil(|py| { - # let fun = pyo3::wrap_pyfunction!(add, py)?; - # - # let doc: String = fun.getattr("__doc__")?.extract()?; - # assert_eq!(doc, "This function adds two unsigned 64-bit integers."); - # - # let inspect = PyModule::import(py, "inspect")?.getattr("signature")?; - # let sig: String = inspect - # .call1((fun,))? - # .call_method0("__str__")? - # .extract()?; - # assert_eq!(sig, "(a, b, /)"); - # - # Ok(()) - # }) - # } - ``` + Overrides the PyO3-generated function signature visible in Python tooling (such as via [`inspect.signature`]). See the [corresponding topic in the Function Signatures subchapter](./function/signature.md#making-the-function-signature-available-to-python). - `#[pyo3(pass_module)]` @@ -161,47 +129,6 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties ## Advanced function patterns -### Making the function signature available to Python (old method) - -Alternatively, simply make sure the first line of your docstring is -formatted like in the following example. Please note that the newline after the -`--` is mandatory. The `/` signifies the end of positional-only arguments. - -`#[pyo3(text_signature)]` should be preferred, since it will override automatically -generated signatures when those are added in a future version of PyO3. - -```rust -# #![allow(dead_code)] -use pyo3::prelude::*; - -/// add(a, b, /) -/// -- -/// -/// This function adds two unsigned 64-bit integers. -#[pyfunction] -fn add(a: u64, b: u64) -> u64 { - a + b -} - -// a function with a signature but without docs. Both blank lines after the `--` are mandatory. - -/// sub(a, b, /) -/// -- -#[pyfunction] -fn sub(a: u64, b: u64) -> u64 { - a - b -} -``` - -When annotated like this, signatures are also correctly displayed in IPython. - -```text ->>> pyo3_test.add? -Signature: pyo3_test.add(a, b, /) -Docstring: This function adds two unsigned 64-bit integers. -Type: builtin_function_or_method -``` - ### Calling Python functions in Rust You can pass Python `def`'d functions and built-in functions to Rust functions [`PyFunction`] diff --git a/guide/src/function/signature.md b/guide/src/function/signature.md index 39e733089d4..cf4f91bed55 100644 --- a/guide/src/function/signature.md +++ b/guide/src/function/signature.md @@ -188,3 +188,130 @@ impl MyClass { } } ``` + +## Making the function signature available to Python + +The function signature is exposed to Python via the `__text_signature__` attribute. PyO3 automatically generates this for every `#[pyfunction]` and all `#[pymethods]`. + +This automatic generation has some limitations, which may be improved in the future: +- It will not include the value of default arguments, replacing them all with `...`. (`.pyi` type stub files commonly also use `...` for all default arguments in the same way.) +- Nothing is generated for the `#[new]` method of a `#[pyclass]`. + +In cases where the automatically-generated signature needs adjusting, it can [be overridden](#overriding-the-generated-signature) using the `#[pyo3(text_signature)]` option.) + +The example below creates a function `add` which accepts two positional-only arguments `a` and `b`, where `b` has a default value of zero. + +```rust +use pyo3::prelude::*; + +/// This function adds two unsigned 64-bit integers. +#[pyfunction] +#[pyo3(signature = (a, b=0, /))] +fn add(a: u64, b: u64) -> u64 { + a + b +} +# +# fn main() -> PyResult<()> { +# Python::with_gil(|py| { +# let fun = pyo3::wrap_pyfunction!(add, py)?; +# +# let doc: String = fun.getattr("__doc__")?.extract()?; +# assert_eq!(doc, "This function adds two unsigned 64-bit integers."); +# +# let inspect = PyModule::import(py, "inspect")?.getattr("signature")?; +# let sig: String = inspect +# .call1((fun,))? +# .call_method0("__str__")? +# .extract()?; +# assert_eq!(sig, "(a, b, /)"); +# +# Ok(()) +# }) +# } +``` + +The following IPython output demonstrates how this generated signature will be seen from Python tooling: + +```text +>>> pyo3_test.add.__text_signature__ +'(a, b=..., /)' +>>> pyo3_test.add? +Signature: pyo3_test.add(a, b=Ellipsis, /) +Docstring: This function adds two unsigned 64-bit integers. +Type: builtin_function_or_method +``` + +### Overriding the generated signature + +The `#[pyo3(text_signature = "()")]` attribute can be used to override the default generated signature. + +In the snippet below, the text signature attribute is used to include the default value of `0` for the argument `b`, instead of the automatically-generated default value of `...`: + +```rust +use pyo3::prelude::*; + +/// This function adds two unsigned 64-bit integers. +#[pyfunction] +#[pyo3(signature = (a, b=0, /), text_signature = "(a, b=0, /)")] +fn add(a: u64, b: u64) -> u64 { + a + b +} +# +# fn main() -> PyResult<()> { +# Python::with_gil(|py| { +# let fun = pyo3::wrap_pyfunction!(add, py)?; +# +# let doc: String = fun.getattr("__doc__")?.extract()?; +# assert_eq!(doc, "This function adds two unsigned 64-bit integers."); +# assert!(fun.getattr("__text_signature__")?.is_none()); +# +# Ok(()) +# }) +# } +``` + +PyO3 will include the contents of the annotation unmodified as the `__text_signature`. Below shows how IPython will now present this (see the default value of 0 for b): + +```text +>>> pyo3_test.add.__text_signature__ +'(a, b=0, /)' +>>> pyo3_test.add? +Signature: pyo3_test.add(a, b=0, /) +Docstring: This function adds two unsigned 64-bit integers. +Type: builtin_function_or_method +``` + +If no signature is wanted at all, `#[pyo3(text_signature = false)]` will disable the built-in signature. The snippet below demonstrates use of this: + +```rust +use pyo3::prelude::*; + +/// This function adds two unsigned 64-bit integers. +#[pyfunction] +#[pyo3(signature = (a, b=0, /), text_signature = false)] +fn add(a: u64, b: u64) -> u64 { + a + b +} +# +# fn main() -> PyResult<()> { +# Python::with_gil(|py| { +# let fun = pyo3::wrap_pyfunction!(add, py)?; +# +# let doc: String = fun.getattr("__doc__")?.extract()?; +# assert_eq!(doc, "This function adds two unsigned 64-bit integers."); +# assert!(fun.getattr("__text_signature__")?.is_none()); +# +# Ok(()) +# }) +# } +``` + +Now the function's `__text_signature__` will be set to `None`, and IPython will not display any signature in the help: + +```text +>>> pyo3_test.add.__text_signature__ == None +True +>>> pyo3_test.add? +Docstring: This function adds two unsigned 64-bit integers. +Type: builtin_function_or_method +```