Add `extract_bound` method to `FromPyObject`

This commit is contained in:
David Hewitt 2023-12-27 08:56:01 +00:00
parent eb8d11f42f
commit 595ca4b3c1
3 changed files with 51 additions and 5 deletions

View File

@ -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

View File

@ -0,0 +1 @@
Add `FromPyObject::extract_bound` method, which can be implemented to avoid using the GIL Ref API in `FromPyObject` implementations.

View File

@ -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;
@ -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.
///