Skip to content

Commit

Permalink
docs: add narrative docs for BoundObject (#4703)
Browse files Browse the repository at this point in the history
* docs: add narrative docs for BoundObject

* grammar fixes

* use where to break up signature

* fix incorrect last sentence

* Update guide/src/types.md

Co-authored-by: Icxolu <[email protected]>

* move example to traits.md and rework with a stronger motivation

* fix cross-reference target

* fix type

* build result vec without copying

* simplify example a little

* update explanation sentence

* fix API docs link

---------

Co-authored-by: Icxolu <[email protected]>
  • Loading branch information
ngoldbaum and Icxolu authored Nov 15, 2024
1 parent ee229cf commit 71100db
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 0 deletions.
68 changes: 68 additions & 0 deletions guide/src/conversions/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,73 @@ impl IntoPy<PyObject> for MyPyObjectWrapper {
}
```

#### `BoundObject` for conversions that may be `Bound` or `Borrowed`

`IntoPyObject::into_py_object` returns either `Bound` or `Borrowed` depending on the implementation for a concrete type. For example, the `IntoPyObject` implementation for `u32` produces a `Bound<'py, PyInt>` and the `bool` implementation produces a `Borrowed<'py, 'py, PyBool>`:

```rust
use pyo3::prelude::*;
use pyo3::IntoPyObject;
use pyo3::types::{PyBool, PyInt};

let ints: Vec<u32> = vec![1, 2, 3, 4];
let bools = vec![true, false, false, true];

Python::with_gil(|py| {
let ints_as_pyint: Vec<Bound<'_, PyInt>> = ints
.iter()
.map(|x| Ok(x.into_pyobject(py)?))
.collect::<PyResult<_>>()
.unwrap();

let bools_as_pybool: Vec<Borrowed<'_, '_, PyBool>> = bools
.iter()
.map(|x| Ok(x.into_pyobject(py)?))
.collect::<PyResult<_>>()
.unwrap();
});
```

In this example if we wanted to combine `ints_as_pyints` and `bools_as_pybool` into a single `Vec<Py<PyAny>>` to return from the `with_gil` closure, we would have to manually convert the concrete types for the smart pointers and the python types.

Instead, we can write a function that generically converts vectors of either integers or bools into a vector of `Py<PyAny>` using the [`BoundObject`] trait:

```rust
# use pyo3::prelude::*;
# use pyo3::BoundObject;
# use pyo3::IntoPyObject;

# let bools = vec![true, false, false, true];
# let ints = vec![1, 2, 3, 4];

fn convert_to_vec_of_pyobj<'py, T>(py: Python<'py>, the_vec: Vec<T>) -> PyResult<Vec<Py<PyAny>>>
where
T: IntoPyObject<'py> + Copy
{
the_vec.iter()
.map(|x| {
Ok(
x.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.unbind()
)
})
.collect()
}

let vec_of_pyobjs: Vec<Py<PyAny>> = Python::with_gil(|py| {
let mut bools_as_pyany = convert_to_vec_of_pyobj(py, bools).unwrap();
let mut ints_as_pyany = convert_to_vec_of_pyobj(py, ints).unwrap();
let mut result: Vec<Py<PyAny>> = vec![];
result.append(&mut bools_as_pyany);
result.append(&mut ints_as_pyany);
result
});
```

In the example above we used `BoundObject::into_any` and `BoundObject::unbind` to manipulate the python types and smart pointers into the result type we wanted to produce from the function.

### The `ToPyObject` trait

<div class="warning">
Expand All @@ -647,3 +714,4 @@ same purpose, except that it consumes `self`.

[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html
[`BoundObject`]: {{#PYO3_DOCS_URL}}/pyo3/instance/trait.BoundObject.html
2 changes: 2 additions & 0 deletions guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ need to adapt an implementation of `IntoPyObject` to stay compatible with the Py
the new [`#[derive(IntoPyObject)]`](#intopyobject-derive-macro) macro can be used instead of
[manual implementations](#intopyobject-manual-implementation).

Since `IntoPyObject::into_pyobject` may return either a `Bound` or `Borrowed`, you may find the [`BoundObject`](conversions/traits.md#boundobject-for-conversions-that-may-be-bound-or-borrowed) trait to be useful to write code that generically handles either type of smart pointer.

Together with the introduction of `IntoPyObject` the old conversion traits `ToPyObject` and `IntoPy`
are deprecated and will be removed in a future PyO3 version.

Expand Down

0 comments on commit 71100db

Please sign in to comment.