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: There are also a few special types related to the GIL and Rust-defined `#[pyclass]`es which may come in useful:
| What | Description | | What | Description |
| ------------- | ------------------------------- | | ------------- | ------------------------------------- |
| `Python` | A GIL token, used to pass to PyO3 constructors to prove ownership of the GIL | | `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>` | 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. |
| `Py<T>` | Same as above, for a specific Python type or `#[pyclass]` T. | | `PyObject` | An alias for `Py<PyAny>` |
| `&PyCell<T>` | A `#[pyclass]` value owned by Python. | | `&PyCell<T>` | A `#[pyclass]` value owned by Python. |
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. | | `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. | | `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). 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 and [`PyRefMut`]. They work like the reference wrappers of
`std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed. `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 ### 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 [`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 [`FromPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.FromPyObject.html
[`ToPyObject`]: https://docs.rs/pyo3/latest/pyo3/conversion/trait.ToPyObject.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 [`PyTuple`]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyTuple.html
[`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html [`PyAny`]: https://docs.rs/pyo3/latest/pyo3/struct.PyAny.html
[`IntoPyDict`]: https://docs.rs/pyo3/latest/pyo3/types/trait.IntoPyDict.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); pyo3::import_exception!(io, UnsupportedOperation);
} }
fn tell(file: PyObject) -> PyResult<u64> { fn tell(file: &PyAny) -> PyResult<u64> {
use pyo3::exceptions::*; use pyo3::exceptions::*;
let gil = Python::acquire_gil(); match file.call_method0("tell") {
let py = gil.python();
match file.call_method0(py, "tell") {
Err(_) => Err(io::UnsupportedOperation::py_err("not supported: 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::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::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 [`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 [`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. 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) 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 ## from 0.10.* to 0.11
### Stable Rust ### 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 * It provides some global API for the Python interpreter, such as
[`eval`][eval]. [`eval`][eval].
* It can be passed to functions that require a proof of holding the GIL, * 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 * Its lifetime can be used to create Rust references that implicitly guarantee
holding the GIL, such as [`&'py PyAny`][PyAny]. holding the GIL, such as [`&'py PyAny`][PyAny].
@ -71,10 +71,7 @@ let obj: &PyAny = PyList::empty(py);
// Convert to &ConcreteType using PyAny::downcast // Convert to &ConcreteType using PyAny::downcast
let _: &PyList = obj.downcast().unwrap(); let _: &PyList = obj.downcast().unwrap();
// Convert to PyObject using .into() or .to_object(py) // Convert to Py<PyAny> (aka PyObject) using .into()
let _: PyObject = obj.into();
// Convert to Py<PyAny> using .into() or Py::from
let _: Py<PyAny> = obj.into(); let _: Py<PyAny> = obj.into();
// Convert to Py<ConcreteType> using PyAny::extract // Convert to Py<ConcreteType> using PyAny::extract
@ -115,51 +112,24 @@ let _: &PyAny = list;
// For more explicit &PyAny conversion, use .as_ref() // For more explicit &PyAny conversion, use .as_ref()
let _: &PyAny = list.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() // To convert to Py<T> use .into() or Py::from()
let _: Py<PyList> = list.into(); 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 **Represents:** a GIL independent reference to a Python object. This can be a Python native type
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, **Used:** Whenever you want to carry around references to a Python object without caring about a
without caring about a GIL lifetime. For example, storing Python object GIL lifetime. For example, storing Python object references in a Rust struct that outlives the
references in a Rust struct that outlives the Python-Rust FFI boundary, Python-Rust FFI boundary, or returning objects from functions implemented in Rust back to Python.
or returning objects from functions implemented in Rust back to Python.
Can be cloned using Python reference counts with `.clone_ref()`. Can be cloned using Python reference counts with `.clone()`.
**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.
**Conversions:** **Conversions:**
@ -178,10 +148,6 @@ let _: &PyList = list.as_ref(py);
let _: PyObject = list.into(); let _: PyObject = list.into();
``` ```
**Note:** `PyObject` is semantically equivalent to `Py<PyAny>` and might be
merged with it in the future.
### `PyCell<SomeType>` ### `PyCell<SomeType>`
**Represents:** a reference to a Rust object (instance of `PyClass`) which is **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 [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 [pyo3::types]: https://docs.rs/pyo3/latest/pyo3/types/index.html
[PyAny]: https://docs.rs/pyo3/latest/pyo3/types/struct.PyAny.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 [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. //! Conversions between various states of Rust and Python types and their wrappers.
use crate::err::{self, PyDowncastError, PyResult}; use crate::err::{self, PyDowncastError, PyResult};
use crate::instance::PyObject;
use crate::type_object::PyTypeInfo; use crate::type_object::PyTypeInfo;
use crate::types::PyTuple; 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; use std::ptr::NonNull;
/// This trait represents that **we can do zero-cost conversion from the object /// 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. /// 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 { pub trait IntoPy<T>: Sized {
/// Performs the conversion. /// Performs the conversion.
fn into_py(self, py: Python) -> T; 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 /// `FromPyObject` is implemented by various types that can be extracted from
/// a Python object reference. /// a Python object reference.
/// ///
/// Normal usage is through the helper methods `PyObject::extract` or /// Normal usage is through the helper methods `Py::extract` or `PyAny::extract`:
/// `PyAny::extract`:
/// ///
/// ```let obj: PyObject = ...; /// ```rust,ignore
/// let obj: Py<PyAny> = ...;
/// let value: &TargetType = obj.extract(py)?; /// let value: &TargetType = obj.extract(py)?;
/// ///
/// let any: &PyAny = ...; /// 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, /// 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. /// 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 /// The trait's conversion method takes a `&PyAny` argument but is called
/// `FromPyObject` for historical reasons. /// `FromPyObject` for historical reasons.
pub trait FromPyObject<'source>: Sized { 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>; pub type PyObject = Py<PyAny>;
#[cfg(test)] #[cfg(test)]

View File

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

View File

@ -166,9 +166,8 @@ impl PyByteArray {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::exceptions; use crate::exceptions;
use crate::instance::PyObject;
use crate::types::PyByteArray; use crate::types::PyByteArray;
use crate::Python; use crate::{PyObject, Python};
#[test] #[test]
fn test_len() { 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_HOUR, PyDateTime_TIME_GET_MICROSECOND, PyDateTime_TIME_GET_MINUTE,
PyDateTime_TIME_GET_SECOND, PyDateTime_TIME_GET_SECOND,
}; };
use crate::instance::PyObject;
use crate::types::PyTuple; use crate::types::PyTuple;
use crate::{AsPyPointer, PyAny, Python, ToPyObject}; use crate::{AsPyPointer, PyAny, PyObject, Python, ToPyObject};
use std::os::raw::c_int; use std::os::raw::c_int;
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
use std::ptr; use std::ptr;

View File

@ -1,13 +1,12 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::err::{self, PyErr, PyResult}; use crate::err::{self, PyErr, PyResult};
use crate::instance::PyNativeType;
use crate::instance::PyObject;
use crate::types::{PyAny, PyList}; use crate::types::{PyAny, PyList};
#[cfg(not(PyPy))] #[cfg(not(PyPy))]
use crate::IntoPyPointer; use crate::IntoPyPointer;
use crate::{ 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::collections::{BTreeMap, HashMap};
use std::ptr::NonNull; 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 /// You can usually avoid directly working with this type
/// by using [`ToPyObject`](trait.ToPyObject.html) /// by using [`ToPyObject`](trait.ToPyObject.html)
/// and [extract](struct.PyObject.html#method.extract) /// and [extract](struct.PyAny.html#method.extract)
/// with `f32`/`f64`. /// with `f32`/`f64`.
#[repr(transparent)] #[repr(transparent)]
pub struct PyFloat(PyAny); pub struct PyFloat(PyAny);

View File

@ -6,12 +6,11 @@ use crate::err::{PyErr, PyResult};
use crate::exceptions; use crate::exceptions;
use crate::ffi; use crate::ffi;
use crate::instance::PyNativeType; use crate::instance::PyNativeType;
use crate::instance::PyObject;
use crate::pyclass::PyClass; use crate::pyclass::PyClass;
use crate::type_object::PyTypeObject; use crate::type_object::PyTypeObject;
use crate::types::PyTuple; use crate::types::PyTuple;
use crate::types::{PyAny, PyDict, PyList}; 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::ffi::{CStr, CString};
use std::os::raw::c_char; use std::os::raw::c_char;
use std::str; use std::str;

View File

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

View File

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

View File

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