docs: update Python function section of the guide (#3925)

* docs: update Python function section of the guide

* update `pass_module` types

Co-authored-by: David Hewitt <mail@davidhewitt.dev>

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
This commit is contained in:
Icxolu 2024-03-03 10:15:46 +01:00 committed by GitHub
parent 2e56f659ed
commit 00eb014623
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 29 additions and 32 deletions

View File

@ -77,7 +77,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python
- <a name="pass_module" ></a> `#[pyo3(pass_module)]` - <a name="pass_module" ></a> `#[pyo3(pass_module)]`
Set this option to make PyO3 pass the containing module as the first argument to the function. It is then possible to use the module in the function body. The first argument **must** be of type `&PyModule`. Set this option to make PyO3 pass the containing module as the first argument to the function. It is then possible to use the module in the function body. The first argument **must** be of type `&Bound<'_, PyModule>`, `Bound<'_, PyModule>`, or `Py<PyModule>`.
The following example creates a function `pyfunction_with_module` which returns the containing module's name (i.e. `module_with_fn`): The following example creates a function `pyfunction_with_module` which returns the containing module's name (i.e. `module_with_fn`):
@ -103,14 +103,14 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties
- <a name="from_py_with"></a> `#[pyo3(from_py_with = "...")]` - <a name="from_py_with"></a> `#[pyo3(from_py_with = "...")]`
Set this on an option to specify a custom function to convert the function argument from Python to the desired Rust type, instead of using the default `FromPyObject` extraction. The function signature must be `fn(&PyAny) -> PyResult<T>` where `T` is the Rust type of the argument. Set this on an option to specify a custom function to convert the function argument from Python to the desired Rust type, instead of using the default `FromPyObject` extraction. The function signature must be `fn(&Bound<'_, PyAny>) -> PyResult<T>` where `T` is the Rust type of the argument.
The following example uses `from_py_with` to convert the input Python object to its length: The following example uses `from_py_with` to convert the input Python object to its length:
```rust ```rust
use pyo3::prelude::*; use pyo3::prelude::*;
fn get_length(obj: &PyAny) -> PyResult<usize> { fn get_length(obj: &Bound<'_, PyAny>) -> PyResult<usize> {
let length = obj.len()?; let length = obj.len()?;
Ok(length) Ok(length)
} }
@ -121,7 +121,7 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties
} }
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let f = pyo3::wrap_pyfunction!(object_length)(py).unwrap(); # let f = pyo3::wrap_pyfunction_bound!(object_length)(py).unwrap();
# assert_eq!(f.call1((vec![1, 2, 3],)).unwrap().extract::<usize>().unwrap(), 3); # assert_eq!(f.call1((vec![1, 2, 3],)).unwrap().extract::<usize>().unwrap(), 3);
# }); # });
``` ```
@ -134,11 +134,11 @@ You can pass Python `def`'d functions and built-in functions to Rust functions [
corresponds to regular Python functions while [`PyCFunction`] describes built-ins such as corresponds to regular Python functions while [`PyCFunction`] describes built-ins such as
`repr()`. `repr()`.
You can also use [`PyAny::is_callable`] to check if you have a callable object. `is_callable` will You can also use [`Bound<'_, PyAny>::is_callable`] to check if you have a callable object. `is_callable`
return `true` for functions (including lambdas), methods and objects with a `__call__` method. will return `true` for functions (including lambdas), methods and objects with a `__call__` method.
You can call the object with [`PyAny::call`] with the args as first parameter and the kwargs You can call the object with [`Bound<'_, PyAny>::call`] with the args as first parameter and the kwargs
(or `None`) as second parameter. There are also [`PyAny::call0`] with no args and [`PyAny::call1`] (or `None`) as second parameter. There are also [`Bound<'_, PyAny>::call0`] with no args and
with only positional args. [`Bound<'_, PyAny>::call1`] with only positional args.
### Calling Rust functions in Python ### Calling Rust functions in Python
@ -149,11 +149,10 @@ The ways to convert a Rust function into a Python object vary depending on the f
- use a `#[pyclass]` struct which stores the function as a field and implement `__call__` to call the stored function. - use a `#[pyclass]` struct which stores the function as a field and implement `__call__` to call the stored function.
- use `PyCFunction::new_closure` to create an object directly from the function. - use `PyCFunction::new_closure` to create an object directly from the function.
[`PyAny::is_callable`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#tymethod.is_callable [`Bound<'_, PyAny>::is_callable`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.is_callable
[`PyAny::call`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#tymethod.call [`Bound<'_, PyAny>::call`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.call
[`PyAny::call0`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#tymethod.call0 [`Bound<'_, PyAny>::call0`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.call0
[`PyAny::call1`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#tymethod.call1 [`Bound<'_, PyAny>::call1`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/trait.PyAnyMethods.html#tymethod.call1
[`PyObject`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyObject.html
[`wrap_pyfunction!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.wrap_pyfunction.html [`wrap_pyfunction!`]: {{#PYO3_DOCS_URL}}/pyo3/macro.wrap_pyfunction.html
[`PyFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFunction.html [`PyFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyFunction.html
[`PyCFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyCFunction.html [`PyCFunction`]: {{#PYO3_DOCS_URL}}/pyo3/types/struct.PyCFunction.html
@ -180,7 +179,7 @@ An example of `#[pyfn]` is below:
use pyo3::prelude::*; use pyo3::prelude::*;
#[pymodule] #[pymodule]
fn my_extension(py: Python<'_>, m: &PyModule) -> PyResult<()> { fn my_extension(m: &Bound<'_, PyModule>) -> PyResult<()> {
#[pyfn(m)] #[pyfn(m)]
fn double(x: usize) -> usize { fn double(x: usize) -> usize {
x * 2 x * 2

View File

@ -44,7 +44,7 @@ fn check_positive(x: i32) -> PyResult<()> {
# #
# fn main(){ # fn main(){
# Python::with_gil(|py|{ # Python::with_gil(|py|{
# let fun = pyo3::wrap_pyfunction!(check_positive, py).unwrap(); # let fun = pyo3::wrap_pyfunction_bound!(check_positive, py).unwrap();
# fun.call1((-1,)).unwrap_err(); # fun.call1((-1,)).unwrap_err();
# fun.call1((1,)).unwrap(); # fun.call1((1,)).unwrap();
# }); # });
@ -72,7 +72,7 @@ fn parse_int(x: &str) -> Result<usize, ParseIntError> {
# fn main() { # fn main() {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(parse_int, py).unwrap(); # let fun = pyo3::wrap_pyfunction_bound!(parse_int, py).unwrap();
# let value: usize = fun.call1(("5",)).unwrap().extract().unwrap(); # let value: usize = fun.call1(("5",)).unwrap().extract().unwrap();
# assert_eq!(value, 5); # assert_eq!(value, 5);
# }); # });
@ -132,7 +132,7 @@ fn connect(s: String) -> Result<(), CustomIOError> {
fn main() { fn main() {
Python::with_gil(|py| { Python::with_gil(|py| {
let fun = pyo3::wrap_pyfunction!(connect, py).unwrap(); let fun = pyo3::wrap_pyfunction_bound!(connect, py).unwrap();
let err = fun.call1(("0.0.0.0",)).unwrap_err(); let err = fun.call1(("0.0.0.0",)).unwrap_err();
assert!(err.is_instance_of::<PyOSError>(py)); assert!(err.is_instance_of::<PyOSError>(py));
}); });
@ -224,7 +224,7 @@ fn wrapped_get_x() -> Result<i32, MyOtherError> {
# fn main() { # fn main() {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(wrapped_get_x, py).unwrap(); # let fun = pyo3::wrap_pyfunction_bound!(wrapped_get_x, py).unwrap();
# let value: usize = fun.call0().unwrap().extract().unwrap(); # let value: usize = fun.call0().unwrap().extract().unwrap();
# assert_eq!(value, 5); # assert_eq!(value, 5);
# }); # });
@ -234,8 +234,6 @@ fn wrapped_get_x() -> Result<i32, MyOtherError> {
[`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html [`From`]: https://doc.rust-lang.org/stable/std/convert/trait.From.html
[`Result<T, E>`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html [`Result<T, E>`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html
[`PyResult<T>`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/type.PyResult.html
[`PyResult<T>`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/type.PyResult.html [`PyResult<T>`]: {{#PYO3_DOCS_URL}}/pyo3/prelude/type.PyResult.html
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html [`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
[`pyo3::exceptions`]: {{#PYO3_DOCS_URL}}/pyo3/exceptions/index.html [`pyo3::exceptions`]: {{#PYO3_DOCS_URL}}/pyo3/exceptions/index.html

View File

@ -16,7 +16,7 @@ use pyo3::types::PyDict;
#[pyfunction] #[pyfunction]
#[pyo3(signature = (**kwds))] #[pyo3(signature = (**kwds))]
fn num_kwds(kwds: Option<&PyDict>) -> usize { fn num_kwds(kwds: Option<&Bound<'_, PyDict>>) -> usize {
kwds.map_or(0, |dict| dict.len()) kwds.map_or(0, |dict| dict.len())
} }
@ -31,8 +31,8 @@ Just like in Python, the following constructs can be part of the signature::
* `/`: positional-only arguments separator, each parameter defined before `/` is a positional-only parameter. * `/`: positional-only arguments separator, each parameter defined before `/` is a positional-only parameter.
* `*`: var arguments separator, each parameter defined after `*` is a keyword-only parameter. * `*`: var arguments separator, each parameter defined after `*` is a keyword-only parameter.
* `*args`: "args" is var args. Type of the `args` parameter has to be `&PyTuple`. * `*args`: "args" is var args. Type of the `args` parameter has to be `&Bound<'_, PyTuple>`.
* `**kwargs`: "kwargs" receives keyword arguments. The type of the `kwargs` parameter has to be `Option<&PyDict>`. * `**kwargs`: "kwargs" receives keyword arguments. The type of the `kwargs` parameter has to be `Option<&Bound<'_, PyDict>>`.
* `arg=Value`: arguments with default value. * `arg=Value`: arguments with default value.
If the `arg` argument is defined after var arguments, it is treated as a keyword-only argument. If the `arg` argument is defined after var arguments, it is treated as a keyword-only argument.
Note that `Value` has to be valid rust code, PyO3 just inserts it into the generated Note that `Value` has to be valid rust code, PyO3 just inserts it into the generated
@ -59,9 +59,9 @@ impl MyClass {
fn method( fn method(
&mut self, &mut self,
num: i32, num: i32,
py_args: &PyTuple, py_args: &Bound<'_, PyTuple>,
name: &str, name: &str,
py_kwargs: Option<&PyDict>, py_kwargs: Option<&Bound<'_, PyDict>>,
) -> String { ) -> String {
let num_before = self.num; let num_before = self.num;
self.num = num; self.num = num;
@ -136,7 +136,7 @@ fn increment(x: u64, amount: Option<u64>) -> u64 {
# #
# fn main() -> PyResult<()> { # fn main() -> PyResult<()> {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(increment, py)?; # let fun = pyo3::wrap_pyfunction_bound!(increment, py)?;
# #
# let inspect = PyModule::import_bound(py, "inspect")?.getattr("signature")?; # let inspect = PyModule::import_bound(py, "inspect")?.getattr("signature")?;
# let sig: String = inspect # let sig: String = inspect
@ -164,7 +164,7 @@ fn increment(x: u64, amount: Option<u64>) -> u64 {
# #
# fn main() -> PyResult<()> { # fn main() -> PyResult<()> {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(increment, py)?; # let fun = pyo3::wrap_pyfunction_bound!(increment, py)?;
# #
# let inspect = PyModule::import_bound(py, "inspect")?.getattr("signature")?; # let inspect = PyModule::import_bound(py, "inspect")?.getattr("signature")?;
# let sig: String = inspect # let sig: String = inspect
@ -204,7 +204,7 @@ fn add(a: u64, b: u64) -> u64 {
# #
# fn main() -> PyResult<()> { # fn main() -> PyResult<()> {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(add, py)?; # let fun = pyo3::wrap_pyfunction_bound!(add, py)?;
# #
# let doc: String = fun.getattr("__doc__")?.extract()?; # let doc: String = fun.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "This function adds two unsigned 64-bit integers."); # assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
@ -252,7 +252,7 @@ fn add(a: u64, b: u64) -> u64 {
# #
# fn main() -> PyResult<()> { # fn main() -> PyResult<()> {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(add, py)?; # let fun = pyo3::wrap_pyfunction_bound!(add, py)?;
# #
# let doc: String = fun.getattr("__doc__")?.extract()?; # let doc: String = fun.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "This function adds two unsigned 64-bit integers."); # assert_eq!(doc, "This function adds two unsigned 64-bit integers.");
@ -269,7 +269,7 @@ fn add(a: u64, b: u64) -> u64 {
# } # }
``` ```
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): 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 ```text
>>> pyo3_test.add.__text_signature__ >>> pyo3_test.add.__text_signature__
@ -294,7 +294,7 @@ fn add(a: u64, b: u64) -> u64 {
# #
# fn main() -> PyResult<()> { # fn main() -> PyResult<()> {
# Python::with_gil(|py| { # Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(add, py)?; # let fun = pyo3::wrap_pyfunction_bound!(add, py)?;
# #
# let doc: String = fun.getattr("__doc__")?.extract()?; # let doc: String = fun.getattr("__doc__")?.extract()?;
# assert_eq!(doc, "This function adds two unsigned 64-bit integers."); # assert_eq!(doc, "This function adds two unsigned 64-bit integers.");