Rewrite documentation for FromPy and PyObject changes

This commit is contained in:
David Hewitt 2020-08-06 22:29:05 +01:00
parent f29e6bae7a
commit 20959ecc92
16 changed files with 130 additions and 98 deletions

View File

@ -44,13 +44,13 @@ The table below contains the Python type and the corresponding function argument
There are also a few special types related to the GIL and Rust-defined `#[pyclass]`es which may come in useful:
| What | Description |
| ------------- | ------------------------------- |
| ------------- | ------------------------------------- |
| `Python` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL |
| `PyObject` | A Python object isolated from the GIL lifetime. This can be sent to other threads. To call Python APIs using this object, it must be used with `AsPyRef::as_ref` to get a `&PyAny` reference. |
| `Py<T>` | Same as above, for a specific Python type or `#[pyclass]` T. |
| `Py<T>` | A Python object isolated from the GIL lifetime. This can be sent to other threads. To call Python APIs using this object, it must be used with `AsPyRef::as_ref` to get an `&T` reference bound to the GIL. |
| `PyObject` | An alias for `Py<PyAny>` |
| `&PyCell<T>` | A `#[pyclass]` value owned by Python. |
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. |
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. |
For more detail on accepting `#[pyclass]` values as function arguments, see [the section of this guide on Python Classes](class.md).
@ -119,6 +119,28 @@ mutable references, you have to extract the PyO3 reference wrappers [`PyRef`]
and [`PyRefMut`]. They work like the reference wrappers of
`std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed.
### `IntoPy<T>`
This trait defines the to-python conversion for a Rust type. It is usually implemented as
`IntoPy<PyObject>`, which is the trait needed for returning a value from `#[pyfunction]` and
`#[pymethods]`.
All types in PyO3 implement this trait, as does a `#[pyclass]` which doesn't use `extends`.
Occasionally you may choose to implement this for custom types which are mapped to Python types
_without_ having a unique python type.
```
use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);
impl IntoPy<PyObject> for MyPyObjectWrapper {
fn into_py(self, py: Python) -> PyObject {
self.0
}
}
```
### The `ToPyObject` trait
@ -216,22 +238,10 @@ fn main() {
}
```
### `FromPy<T>` and `IntoPy<T>`
Many conversions in PyO3 can't use `std::convert::From` because they need a GIL token.
The [`FromPy`] trait offers an `from_py` method that works just like `from`, except for taking a `Python<'_>` argument.
I.e. `FromPy<T>` could be converting a Rust object into a Python object even though it is called [`FromPy`] - it doesn't say anything about which side of the conversion is a Python object.
Just like `From<T>`, if you implement `FromPy<T>` you gain a blanket implementation of [`IntoPy`] for free.
Eventually, traits such as [`ToPyObject`] will be replaced by this trait and a [`FromPy`] trait will be added that will implement
[`IntoPy`], just like with `From` and `Into`.
[`IntoPy`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.IntoPy.html
[`FromPy`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPy.html
[`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
[`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.html
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html
[`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html
[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html
[`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.html

View File

@ -192,15 +192,12 @@ mod io {
pyo3::import_exception!(io, UnsupportedOperation);
}
fn tell(file: PyObject) -> PyResult<u64> {
fn tell(file: &PyAny) -> PyResult<u64> {
use pyo3::exceptions::*;
let gil = Python::acquire_gil();
let py = gil.python();
match file.call_method0(py, "tell") {
match file.call_method0("tell") {
Err(_) => Err(io::UnsupportedOperation::py_err("not supported: tell")),
Ok(x) => x.extract::<u64>(py),
Ok(x) => x.extract::<u64>(),
}
}

View File

@ -187,5 +187,5 @@ If you have a static function, you can expose it with `#[pyfunction]` and use [`
[`PyAny::call`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call
[`PyAny::call0`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call0
[`PyAny::call1`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html#tymethod.call1
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html
[`PyObject`]: https://docs.rs/pyo3/latest/pyo3/type.PyObject.html
[`wrap_pyfunction!`]: https://docs.rs/pyo3/latest/pyo3/macro.wrap_pyfunction.html

View File

@ -3,6 +3,65 @@
This guide can help you upgrade code through breaking changes from one PyO3 version to the next.
For a detailed list of all changes, see [CHANGELOG.md](https://github.com/PyO3/pyo3/blob/master/CHANGELOG.md)
## from 0.11.* to 0.12
### `FromPy` has been removed
To simplify the PyO3 public conversion trait hierarchy, the `FromPy` has been removed. In PyO3
`0.11` there were two ways to define the to-Python conversion for a type: `FromPy<T> for PyObject`,
and `IntoPy<PyObject> for T`.
Now, the canonical implementation is always `IntoPy`, so downstream crates may need to adjust
accordingly.
Before:
```rust,ignore
# use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);
impl FromPy<MyPyObjectWrapper> for PyObject {
fn from_py(other: MyPyObjectWrapper, _py: Python) -> Self {
other.0
}
}
```
After
```rust
# use pyo3::prelude::*;
struct MyPyObjectWrapper(PyObject);
impl IntoPy<PyObject> for MyPyObjectWrapper {
fn into_py(self, _py: Python) -> PyObject {
self.0
}
}
```
Similarly, code which was using the `FromPy` trait can be trivially rewritten to use `IntoPy`.
Before:
```rust,ignore
# use pyo3::prelude::*;
# Python::with_gil(|py| {
let obj = PyObject::from_py(1.234, py);
# })
```
After:
```rust
# use pyo3::prelude::*;
# Python::with_gil(|py| {
let obj: PyObject = 1.234.into_py(py);
# })
```
### `PyObject` is now a type alias of `Py<PyAny>`
This should change very little from a usage perspective. If you implemented traits for both
`PyObject` and `Py<T>`, you may find you can just remove the `PyObject` implementation.
## from 0.10.* to 0.11
### Stable Rust

View File

@ -24,7 +24,7 @@ In PyO3, holding the GIL is modeled by acquiring a token of the type
* It provides some global API for the Python interpreter, such as
[`eval`][eval].
* It can be passed to functions that require a proof of holding the GIL,
such as [`PyObject::clone_ref`][clone_ref].
such as [`Py::clone_ref`][clone_ref].
* Its lifetime can be used to create Rust references that implicitly guarantee
holding the GIL, such as [`&'py PyAny`][PyAny].
@ -71,10 +71,7 @@ let obj: &PyAny = PyList::empty(py);
// Convert to &ConcreteType using PyAny::downcast
let _: &PyList = obj.downcast().unwrap();
// Convert to PyObject using .into() or .to_object(py)
let _: PyObject = obj.into();
// Convert to Py<PyAny> using .into() or Py::from
// Convert to Py<PyAny> (aka PyObject) using .into()
let _: Py<PyAny> = obj.into();
// Convert to Py<ConcreteType> using PyAny::extract
@ -115,51 +112,24 @@ let _: &PyAny = list;
// For more explicit &PyAny conversion, use .as_ref()
let _: &PyAny = list.as_ref();
// To convert to PyObject use .into() or .to_object(py)
let _: PyObject = list.into();
// To convert to Py<T> use .into() or Py::from()
let _: Py<PyList> = list.into();
// To convert to PyObject use .into() or .to_object(py)
let _: PyObject = list.into();
```
### `PyObject`
### `Py<T>`
**Represents:** a GIL independent reference to a Python object of unspecified
type.
**Represents:** a GIL independent reference to a Python object. This can be a Python native type
(like `PyTuple`), or a `pyclass` type implemented in Rust. The most commonly-used variant,
`Py<PyAny>`, is also known as `PyObject`.
**Used:** Whenever you want to carry around references to "some" Python object,
without caring about a GIL lifetime. For example, storing Python object
references in a Rust struct that outlives the Python-Rust FFI boundary,
or returning objects from functions implemented in Rust back to Python.
**Used:** Whenever you want to carry around references to a Python object without caring about a
GIL lifetime. For example, storing Python object references in a Rust struct that outlives the
Python-Rust FFI boundary, or returning objects from functions implemented in Rust back to Python.
Can be cloned using Python reference counts with `.clone_ref()`.
**Conversions:**
```rust
# use pyo3::prelude::*;
# use pyo3::types::PyList;
# let gil = Python::acquire_gil();
# let py = gil.python();
let obj: PyObject = PyList::empty(py).into();
// Convert to &PyAny using AsPyRef::as_ref
let _: &PyAny = obj.as_ref(py);
// Convert to &ConcreteType using PyObject::cast_as
let _: &PyList = obj.cast_as(py).unwrap();
// Convert to Py<ConcreteType> using PyObject::extract
let _: Py<PyList> = obj.extract(py).unwrap();
```
### `Py<SomeType>`
**Represents:** a GIL independent reference to a Python object of known type.
This can be a Python native type (like `PyTuple`), or a `pyclass` type
implemented in Rust.
**Used:** Like `PyObject`, but with a known inner type.
Can be cloned using Python reference counts with `.clone()`.
**Conversions:**
@ -178,10 +148,6 @@ let _: &PyList = list.as_ref(py);
let _: PyObject = list.into();
```
**Note:** `PyObject` is semantically equivalent to `Py<PyAny>` and might be
merged with it in the future.
### `PyCell<SomeType>`
**Represents:** a reference to a Rust object (instance of `PyClass`) which is
@ -248,7 +214,7 @@ This trait marks structs that mirror native Python types, such as `PyList`.
[eval]: https://docs.rs/pyo3/latest/pyo3/struct.Python.html#method.eval
[clone_ref]: https://docs.rs/pyo3/latest/pyo3/struct.PyObject.html#method.clone_ref
[clone_ref]: https://docs.rs/pyo3/latest/pyo3/struct.Py.html#method.clone_ref
[pyo3::types]: https://docs.rs/pyo3/latest/pyo3/types/index.html
[PyAny]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.html
[PyList_append]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyList.html#method.append

View File

@ -2,10 +2,11 @@
//! Conversions between various states of Rust and Python types and their wrappers.
use crate::err::{self, PyDowncastError, PyResult};
use crate::instance::PyObject;
use crate::type_object::PyTypeInfo;
use crate::types::PyTuple;
use crate::{ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyRef, PyRefMut, Python};
use crate::{
ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python,
};
use std::ptr::NonNull;
/// This trait represents that **we can do zero-cost conversion from the object
@ -133,6 +134,9 @@ where
}
/// Similar to [std::convert::Into], just that it requires a gil token.
///
/// `IntoPy<PyObject>` (aka `IntoPy<Py<PyAny>>`) should be implemented to define a conversion from
/// Rust to Python which can be used by most of PyO3's methods.
pub trait IntoPy<T>: Sized {
/// Performs the conversion.
fn into_py(self, py: Python) -> T;
@ -141,10 +145,10 @@ pub trait IntoPy<T>: Sized {
/// `FromPyObject` is implemented by various types that can be extracted from
/// a Python object reference.
///
/// Normal usage is through the helper methods `PyObject::extract` or
/// `PyAny::extract`:
/// Normal usage is through the helper methods `Py::extract` or `PyAny::extract`:
///
/// ```let obj: PyObject = ...;
/// ```rust,ignore
/// let obj: Py<PyAny> = ...;
/// let value: &TargetType = obj.extract(py)?;
///
/// let any: &PyAny = ...;
@ -161,9 +165,6 @@ 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.
///
/// In cases where the result does not depend on the `'prepared` lifetime,
/// the inherent method `PyObject::extract()` can be used.
///
/// The trait's conversion method takes a `&PyAny` argument but is called
/// `FromPyObject` for historical reasons.
pub trait FromPyObject<'source>: Sized {

View File

@ -558,6 +558,12 @@ impl<T> std::fmt::Debug for Py<T> {
}
}
/// A commonly-used alias for `Py<PyAny>`.
///
/// This is an owned reference a Python object without any type information. This value can also be
/// safely sent between threads.
///
/// See the documentation for [`Py`](struct.Py.html).
pub type PyObject = Py<PyAny>;
#[cfg(test)]

View File

@ -12,8 +12,7 @@
pub use crate::err::{PyErr, PyResult};
pub use crate::gil::GILGuard;
pub use crate::instance::PyObject;
pub use crate::instance::{AsPyRef, Py};
pub use crate::instance::{AsPyRef, Py, PyObject};
pub use crate::pycell::{PyCell, PyRef, PyRefMut};
pub use crate::pyclass_init::PyClassInitializer;
pub use crate::python::Python;

View File

@ -166,9 +166,8 @@ impl PyByteArray {
#[cfg(test)]
mod test {
use crate::exceptions;
use crate::instance::PyObject;
use crate::types::PyByteArray;
use crate::Python;
use crate::{PyObject, Python};
#[test]
fn test_len() {

View File

@ -25,9 +25,8 @@ use crate::ffi::{
PyDateTime_TIME_GET_HOUR, PyDateTime_TIME_GET_MICROSECOND, PyDateTime_TIME_GET_MINUTE,
PyDateTime_TIME_GET_SECOND,
};
use crate::instance::PyObject;
use crate::types::PyTuple;
use crate::{AsPyPointer, PyAny, Python, ToPyObject};
use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject};
use std::os::raw::c_int;
#[cfg(not(PyPy))]
use std::ptr;

View File

@ -1,13 +1,12 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{self, PyErr, PyResult};
use crate::instance::PyNativeType;
use crate::instance::PyObject;
use crate::types::{PyAny, PyList};
#[cfg(not(PyPy))]
use crate::IntoPyPointer;
use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, PyTryFrom, Python, ToBorrowedObject, ToPyObject,
ffi, AsPyPointer, FromPyObject, IntoPy, PyNativeType, PyObject, PyTryFrom, Python,
ToBorrowedObject, ToPyObject,
};
use std::collections::{BTreeMap, HashMap};
use std::ptr::NonNull;

View File

@ -11,7 +11,7 @@ use std::os::raw::c_double;
///
/// You can usually avoid directly working with this type
/// by using [`ToPyObject`](trait.ToPyObject.html)
/// and [extract](struct.PyObject.html#method.extract)
/// and [extract](struct.PyAny.html#method.extract)
/// with `f32`/`f64`.
#[repr(transparent)]
pub struct PyFloat(PyAny);

View File

@ -6,12 +6,11 @@ use crate::err::{PyErr, PyResult};
use crate::exceptions;
use crate::ffi;
use crate::instance::PyNativeType;
use crate::instance::PyObject;
use crate::pyclass::PyClass;
use crate::type_object::PyTypeObject;
use crate::types::PyTuple;
use crate::types::{PyAny, PyDict, PyList};
use crate::{AsPyPointer, IntoPy, Py, Python, ToPyObject};
use crate::{AsPyPointer, IntoPy, Py, PyObject, Python, ToPyObject};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::str;

View File

@ -50,7 +50,7 @@ macro_rules! int_fits_larger_int {
///
/// You can usually avoid directly working with this type
/// by using [`ToPyObject`](trait.ToPyObject.html)
/// and [extract](struct.PyObject.html#method.extract)
/// and [extract](struct.PyAny.html#method.extract)
/// with the primitive Rust integer types.
#[repr(transparent)]
pub struct PyLong(PyAny);

View File

@ -397,11 +397,10 @@ impl<'v> PyTryFrom<'v> for PySequence {
#[cfg(test)]
mod test {
use crate::instance::AsPyRef;
use crate::instance::PyObject;
use crate::types::PySequence;
use crate::AsPyPointer;
use crate::Python;
use crate::{PyTryFrom, ToPyObject};
use crate::{PyObject, PyTryFrom, ToPyObject};
fn get_object() -> PyObject {
// Convenience function for getting a single unique object

View File

@ -147,9 +147,8 @@ impl<'source> FromPyObject<'source> for String {
mod test {
use super::PyString;
use crate::instance::AsPyRef;
use crate::instance::PyObject;
use crate::Python;
use crate::{FromPyObject, PyTryFrom, ToPyObject};
use crate::{FromPyObject, PyObject, PyTryFrom, ToPyObject};
#[test]
fn test_non_bmp() {