Skip to content

Commit

Permalink
Add extract_bound method to FromPyObject
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Jan 28, 2024
1 parent eb8d11f commit 595ca4b
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 5 deletions.
30 changes: 30 additions & 0 deletions guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,42 @@ To minimise breakage of code using the GIL-Refs API, the `Bound<T>` smart pointe

For example, the following APIs have gained updated variants:
- `PyList::new`, `PyTyple::new` and similar constructors have replacements `PyList::new_bound`, `PyTuple::new_bound` etc.
- `FromPyObject::extract` has a new `FromPyObject::extract_bound` (see the section below)

Because the new `Bound<T>` API brings ownership out of the PyO3 framework and into user code, there are a few places where user code is expected to need to adjust while switching to the new API:
- Code will need to add the occasional `&` to borrow the new smart pointer as `&Bound<T>` to pass these types around (or use `.clone()` at the very small cost of increasing the Python reference count)
- `Bound<PyList>` and `Bound<PyTuple>` cannot support indexing with `list[0]`, you should use `list.get_item(0)` instead.
- `Bound<PyTuple>::iter_borrowed` is slightly more efficient than `Bound<PyTuple>::iter`. The default iteration of `Bound<PyTuple>` cannot return borrowed references because Rust does not (yet) have "lending iterators". Similarly `Bound<PyTuple>::get_borrowed_item` is more efficient than `Bound<PyTuple>::get_item` for the same reason.

#### Migrating `FromPyObject` implementations

`FromPyObject` has had a new method `extract_bound` which takes `&Bound<'py, PyAny>` as an argument instead of `&PyAny`. Both `extract` and `extract_bound` have been given default implementations in terms of the other, to avoid breaking code immediately on update to 0.21.

All implementations of `FromPyObject` should be switched from `extract` to `extract_bound`.

Before:

```rust,ignore
impl<'py> FromPyObject<'py> for MyType {
fn extract(obj: &'py PyAny) -> PyResult<Self> {
/* ... */
}
}
```

After:

```rust,ignore
impl<'py> FromPyObject<'py> for MyType {
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
/* ... */
}
}
```


The expectation is that in 0.22 `extract_bound` will have the default implementation removed and in 0.23 `extract` will be removed.

## from 0.19.* to 0.20

### Drop support for older technologies
Expand Down
1 change: 1 addition & 0 deletions newsfragments/3706.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `FromPyObject::extract_bound` method, which can be implemented to avoid using the GIL Ref API in `FromPyObject` implementations.
25 changes: 20 additions & 5 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::pyclass::boolean_struct::False;
use crate::type_object::PyTypeInfo;
use crate::types::PyTuple;
use crate::{
ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python,
ffi, gil, Bound, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python,
};
use std::cell::Cell;
use std::ptr::NonNull;
Expand Down Expand Up @@ -215,11 +215,26 @@ pub trait IntoPy<T>: Sized {
/// Since which case applies depends on the runtime type of the Python object,
/// both the `obj` and `prepared` variables must outlive the resulting string slice.
///
/// The trait's conversion method takes a `&PyAny` argument but is called
/// `FromPyObject` for historical reasons.
/// During the migration of PyO3 from the "GIL Refs" API to the `Bound<T>` smart pointer, this trait
/// has two methods `extract` and `extract_bound` which are defaulted to call each other. To avoid
/// infinite recursion, implementors must implement at least one of these methods. The recommendation
/// is to implement `extract_bound` and leave `extract` as the default implementation.
pub trait FromPyObject<'source>: Sized {
/// Extracts `Self` from the source `PyObject`.
fn extract(ob: &'source PyAny) -> PyResult<Self>;
/// Extracts `Self` from the source GIL Ref `obj`.
///
/// Implementors are encouraged to implement `extract_bound` and leave this method as the
/// default implementation, which will forward calls to `extract_bound`.
fn extract(ob: &'source PyAny) -> PyResult<Self> {
Self::extract_bound(&ob.as_borrowed())
}

/// Extracts `Self` from the bound smart pointer `obj`.
///
/// Implementors are encouraged to implement this method and leave `extract` defaulted, as
/// this will be most compatible with PyO3's future API.
fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
Self::extract(ob.clone().into_gil_ref())
}

/// Extracts the type hint information for this type when it appears as an argument.
///
Expand Down

0 comments on commit 595ca4b

Please sign in to comment.