From 20959ecc92f319a2d91104499fa0e34ab4ef1e09 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Thu, 6 Aug 2020 22:29:05 +0100 Subject: [PATCH] Rewrite documentation for FromPy and PyObject changes --- guide/src/conversions.md | 46 +++++++++++++++++------------ guide/src/exception.md | 9 ++---- guide/src/function.md | 2 +- guide/src/migration.md | 59 ++++++++++++++++++++++++++++++++++++++ guide/src/types.md | 62 +++++++++------------------------------- src/conversion.rs | 17 +++++------ src/instance.rs | 6 ++++ src/prelude.rs | 3 +- src/types/bytearray.rs | 3 +- src/types/datetime.rs | 3 +- src/types/dict.rs | 5 ++-- src/types/floatob.rs | 2 +- src/types/module.rs | 3 +- src/types/num.rs | 2 +- src/types/sequence.rs | 3 +- src/types/string.rs | 3 +- 16 files changed, 130 insertions(+), 98 deletions(-) diff --git a/guide/src/conversions.md b/guide/src/conversions.md index f21c5692..a1d6a6ed 100644 --- a/guide/src/conversions.md +++ b/guide/src/conversions.md @@ -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` | Same as above, for a specific Python type or `#[pyclass]` T. | +| `Py` | 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` | | `&PyCell` | A `#[pyclass]` value owned by Python. | -| `PyRef` | A `#[pyclass]` borrowed immutably. | -| `PyRefMut` | A `#[pyclass]` borrowed mutably. | +| `PyRef` | A `#[pyclass]` borrowed immutably. | +| `PyRefMut` | 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` + +This trait defines the to-python conversion for a Rust type. It is usually implemented as +`IntoPy`, 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 for MyPyObjectWrapper { + fn into_py(self, py: Python) -> PyObject { + self.0 + } +} +``` ### The `ToPyObject` trait @@ -216,22 +238,10 @@ fn main() { } ``` -### `FromPy` and `IntoPy` - -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` 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`, if you implement `FromPy` 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 diff --git a/guide/src/exception.md b/guide/src/exception.md index b69c782c..e2235890 100644 --- a/guide/src/exception.md +++ b/guide/src/exception.md @@ -192,15 +192,12 @@ mod io { pyo3::import_exception!(io, UnsupportedOperation); } -fn tell(file: PyObject) -> PyResult { +fn tell(file: &PyAny) -> PyResult { 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::(py), + Ok(x) => x.extract::(), } } diff --git a/guide/src/function.md b/guide/src/function.md index 110504b0..1a12d8ec 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -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 diff --git a/guide/src/migration.md b/guide/src/migration.md index f1041043..1159b62c 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -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 for PyObject`, +and `IntoPy 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 for PyObject { + fn from_py(other: MyPyObjectWrapper, _py: Python) -> Self { + other.0 + } +} +``` + +After + +```rust +# use pyo3::prelude::*; +struct MyPyObjectWrapper(PyObject); + +impl IntoPy 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` +This should change very little from a usage perspective. If you implemented traits for both +`PyObject` and `Py`, you may find you can just remove the `PyObject` implementation. + ## from 0.10.* to 0.11 ### Stable Rust diff --git a/guide/src/types.md b/guide/src/types.md index 5cb6ab7c..ea19e15b 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -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 using .into() or Py::from +// Convert to Py (aka PyObject) using .into() let _: Py = obj.into(); // Convert to Py 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 use .into() or Py::from() let _: Py = list.into(); + +// To convert to PyObject use .into() or .to_object(py) +let _: PyObject = list.into(); ``` -### `PyObject` +### `Py` -**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`, 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 using PyObject::extract -let _: Py = obj.extract(py).unwrap(); -``` - -### `Py` - -**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` and might be -merged with it in the future. - - ### `PyCell` **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 diff --git a/src/conversion.rs b/src/conversion.rs index e856e2b4..0efce47c 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -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` (aka `IntoPy>`) should be implemented to define a conversion from +/// Rust to Python which can be used by most of PyO3's methods. pub trait IntoPy: Sized { /// Performs the conversion. fn into_py(self, py: Python) -> T; @@ -141,10 +145,10 @@ pub trait IntoPy: 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 = ...; /// let value: &TargetType = obj.extract(py)?; /// /// let any: &PyAny = ...; @@ -161,9 +165,6 @@ pub trait IntoPy: 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 { diff --git a/src/instance.rs b/src/instance.rs index 7f149c4d..22252ef4 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -558,6 +558,12 @@ impl std::fmt::Debug for Py { } } +/// A commonly-used alias for `Py`. +/// +/// 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; #[cfg(test)] diff --git a/src/prelude.rs b/src/prelude.rs index eeb828ea..a3573a11 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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; diff --git a/src/types/bytearray.rs b/src/types/bytearray.rs index 3ae22d49..edc4ebe1 100644 --- a/src/types/bytearray.rs +++ b/src/types/bytearray.rs @@ -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() { diff --git a/src/types/datetime.rs b/src/types/datetime.rs index c38063cf..6d2696ab 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -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; diff --git a/src/types/dict.rs b/src/types/dict.rs index 1b6f9d52..e3916858 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -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; diff --git a/src/types/floatob.rs b/src/types/floatob.rs index d8ed22c5..583fc9ea 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -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); diff --git a/src/types/module.rs b/src/types/module.rs index ede21ff0..1260d802 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -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; diff --git a/src/types/num.rs b/src/types/num.rs index e41a8269..376c104c 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -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); diff --git a/src/types/sequence.rs b/src/types/sequence.rs index 1831f3eb..0c38a86f 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -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 diff --git a/src/types/string.rs b/src/types/string.rs index 580cdf43..78258610 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -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() {