From 92ba7f11421032b25f1b98268e5a079513882c37 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Sun, 10 Mar 2024 12:15:24 +0000 Subject: [PATCH] update remainder of the guide to Bound API --- guide/src/advanced.md | 7 -- guide/src/async-await.md | 12 ++- guide/src/class.md | 10 ++- guide/src/conversions/tables.md | 4 +- guide/src/conversions/traits.md | 2 +- guide/src/ecosystem/async-await.md | 12 +-- guide/src/exception.md | 2 +- guide/src/function.md | 4 +- guide/src/memory.md | 32 ++++++-- guide/src/migration.md | 14 +++- guide/src/performance.md | 42 +++++----- .../python-from-rust/calling-existing-code.md | 21 +++-- guide/src/trait-bounds.md | 80 +++++++------------ guide/src/types.md | 35 ++++---- noxfile.py | 1 - 15 files changed, 144 insertions(+), 134 deletions(-) diff --git a/guide/src/advanced.md b/guide/src/advanced.md index 8264c14dd17..61dc66382f4 100644 --- a/guide/src/advanced.md +++ b/guide/src/advanced.md @@ -5,10 +5,3 @@ PyO3 exposes much of Python's C API through the `ffi` module. The C API is naturally unsafe and requires you to manage reference counts, errors and specific invariants yourself. Please refer to the [C API Reference Manual](https://docs.python.org/3/c-api/) and [The Rustonomicon](https://doc.rust-lang.org/nightly/nomicon/ffi.html) before using any function from that API. - -## Memory management - -PyO3's `&PyAny` "owned references" and `Py` smart pointers are used to -access memory stored in Python's heap. This memory sometimes lives for longer -than expected because of differences in Rust and Python's memory models. See -the chapter on [memory management](./memory.md) for more information. diff --git a/guide/src/async-await.md b/guide/src/async-await.md index c354be7f1b7..0db1720340d 100644 --- a/guide/src/async-await.md +++ b/guide/src/async-await.md @@ -30,13 +30,13 @@ async fn sleep(seconds: f64, result: Option) -> Option { Resulting future of an `async fn` decorated by `#[pyfunction]` must be `Send + 'static` to be embedded in a Python object. -As a consequence, `async fn` parameters and return types must also be `Send + 'static`, so it is not possible to have a signature like `async fn does_not_compile(arg: &PyAny, py: Python<'_>) -> &PyAny`. +As a consequence, `async fn` parameters and return types must also be `Send + 'static`, so it is not possible to have a signature like `async fn does_not_compile<'py>(arg: Bound<'py, PyAny>) -> Bound<'py, PyAny>`. -However, there is an exception for method receiver, so async methods can accept `&self`/`&mut self`. Note that this means that the class instance is borrowed for as long as the returned future is not completed, even across yield points and while waiting for I/O operations to complete. Hence, other methods cannot obtain exclusive borrows while the future is still being polled. This is the same as how async methods in Rust generally work but it is more problematic for Rust code interfacing with Python code due to pervasive shared mutability. This strongly suggests to prefer shared borrows `&self` to exclusive ones `&mut self` to avoid racy borrow check failures at runtime. +However, there is an exception for method receivers, so async methods can accept `&self`/`&mut self`. Note that this means that the class instance is borrowed for as long as the returned future is not completed, even across yield points and while waiting for I/O operations to complete. Hence, other methods cannot obtain exclusive borrows while the future is still being polled. This is the same as how async methods in Rust generally work but it is more problematic for Rust code interfacing with Python code due to pervasive shared mutability. This strongly suggests to prefer shared borrows `&self` to exclusive ones `&mut self` to avoid racy borrow check failures at runtime. ## Implicit GIL holding -Even if it is not possible to pass a `py: Python<'_>` parameter to `async fn`, the GIL is still held during the execution of the future – it's also the case for regular `fn` without `Python<'_>`/`&PyAny` parameter, yet the GIL is held. +Even if it is not possible to pass a `py: Python<'py>` parameter to `async fn`, the GIL is still held during the execution of the future – it's also the case for regular `fn` without `Python<'py>`/`Bound<'py, PyAny>` parameter, yet the GIL is held. It is still possible to get a `Python` marker using [`Python::with_gil`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.with_gil); because `with_gil` is reentrant and optimized, the cost will be negligible. @@ -47,7 +47,11 @@ There is currently no simple way to release the GIL when awaiting a future, *but Here is the advised workaround for now: ```rust,ignore -use std::{future::Future, pin::{Pin, pin}, task::{Context, Poll}}; +use std::{ + future::Future, + pin::{Pin, pin}, + task::{Context, Poll}, +}; use pyo3::prelude::*; struct AllowThreads(F); diff --git a/guide/src/class.md b/guide/src/class.md index 20a78a7f40c..2c6d854ff08 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -60,7 +60,7 @@ enum Shape { Circle { radius: f64 }, Rectangle { width: f64, height: f64 }, RegularPolygon { side_count: u32, radius: f64 }, - Nothing { }, + Nothing {}, } ``` @@ -89,7 +89,7 @@ Currently, the best alternative is to write a macro which expands to a new `#[py use pyo3::prelude::*; struct GenericClass { - data: T + data: T, } macro_rules! create_interface { @@ -102,7 +102,9 @@ macro_rules! create_interface { impl $name { #[new] pub fn new(data: $type) -> Self { - Self { inner: GenericClass { data: data } } + Self { + inner: GenericClass { data: data }, + } } } }; @@ -427,7 +429,7 @@ impl DictWithCounter { Self::default() } - fn set(slf: &Bound<'_, Self>, key: String, value: &PyAny) -> PyResult<()> { + fn set(slf: &Bound<'_, Self>, key: String, value: Bound<'_, PyAny>) -> PyResult<()> { slf.borrow_mut().counter.entry(key.clone()).or_insert(0); let dict = slf.downcast::()?; dict.set_item(key, value) diff --git a/guide/src/conversions/tables.md b/guide/src/conversions/tables.md index e61932f0206..eb33b17acf7 100644 --- a/guide/src/conversions/tables.md +++ b/guide/src/conversions/tables.md @@ -72,9 +72,9 @@ For most PyO3 usage the conversion cost is worth paying to get these benefits. A ### Returning Rust values to Python -When returning values from functions callable from Python, Python-native types (`&PyAny`, `&PyDict` etc.) can be used with zero cost. +When returning values from functions callable from Python, [PyO3's smart pointers](../types.md#pyo3s-smart-pointers) (`Py`, `Bound<'py, T>`, and `Borrowed<'a, 'py, T>`) can be used with zero cost. -Because these types are references, in some situations the Rust compiler may ask for lifetime annotations. If this is the case, you should use `Py`, `Py` etc. instead - which are also zero-cost. For all of these Python-native types `T`, `Py` can be created from `T` with an `.into()` conversion. +Because `Bound<'py, T>` and `Borrowed<'a, 'py, T>` have lifetime parameters, the Rust compiler may ask for lifetime annotations to be added to your function. See the [section of the guide dedicated to this](../types.md#function-argument-lifetimes). If your function is fallible, it should return `PyResult` or `Result` where `E` implements `From for PyErr`. This will raise a `Python` exception if the `Err` variant is returned. diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 5cdf2c590b9..3d1d5e21d85 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -484,7 +484,7 @@ If the input is neither a string nor an integer, the error message will be: - `pyo3(from_py_with = "...")` - apply a custom function to convert the field from Python the desired Rust type. - the argument must be the name of the function as a string. - - the function signature must be `fn(&PyAny) -> PyResult` where `T` is the Rust type of the argument. + - the function signature must be `fn(Bound) -> PyResult` where `T` is the Rust type of the argument. ### `IntoPy` diff --git a/guide/src/ecosystem/async-await.md b/guide/src/ecosystem/async-await.md index 1e4ea4ab4b9..0319fa05063 100644 --- a/guide/src/ecosystem/async-await.md +++ b/guide/src/ecosystem/async-await.md @@ -120,7 +120,7 @@ Export an async function that makes use of `async-std`: use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] -fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> { +fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> { pyo3_asyncio::async_std::future_into_py(py, async { async_std::task::sleep(std::time::Duration::from_secs(1)).await; Ok(Python::with_gil(|py| py.None())) @@ -143,7 +143,7 @@ If you want to use `tokio` instead, here's what your module should look like: use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] -fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> { +fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> { pyo3_asyncio::tokio::future_into_py(py, async { tokio::time::sleep(std::time::Duration::from_secs(1)).await; Ok(Python::with_gil(|py| py.None())) @@ -233,7 +233,7 @@ a coroutine argument: ```rust #[pyfunction] -fn await_coro(coro: &PyAny) -> PyResult<()> { +fn await_coro(coro: &Bound<'_, PyAny>>) -> PyResult<()> { // convert the coroutine into a Rust future using the // async_std runtime let f = pyo3_asyncio::async_std::into_future(coro)?; @@ -261,7 +261,7 @@ If for you wanted to pass a callable function to the `#[pyfunction]` instead, (i ```rust #[pyfunction] -fn await_coro(callable: &PyAny) -> PyResult<()> { +fn await_coro(callable: &Bound<'_, PyAny>>) -> PyResult<()> { // get the coroutine by calling the callable let coro = callable.call0()?; @@ -317,7 +317,7 @@ async fn rust_sleep() { } #[pyfunction] -fn call_rust_sleep(py: Python<'_>) -> PyResult<&PyAny> { +fn call_rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> { pyo3_asyncio::async_std::future_into_py(py, async move { rust_sleep().await; Ok(Python::with_gil(|py| py.None())) @@ -467,7 +467,7 @@ tokio = "1.4" use pyo3::{prelude::*, wrap_pyfunction}; #[pyfunction] -fn rust_sleep(py: Python<'_>) -> PyResult<&PyAny> { +fn rust_sleep(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>>> { pyo3_asyncio::tokio::future_into_py(py, async { tokio::time::sleep(std::time::Duration::from_secs(1)).await; Ok(Python::with_gil(|py| py.None())) diff --git a/guide/src/exception.md b/guide/src/exception.md index ee36f544d74..3e2f5034897 100644 --- a/guide/src/exception.md +++ b/guide/src/exception.md @@ -111,7 +111,7 @@ mod io { pyo3::import_exception!(io, UnsupportedOperation); } -fn tell(file: &PyAny) -> PyResult { +fn tell(file: &Bound<'_, PyAny>) -> PyResult { match file.call_method0("tell") { Err(_) => Err(io::UnsupportedOperation::new_err("not supported: tell")), Ok(x) => x.extract::(), diff --git a/guide/src/function.md b/guide/src/function.md index cfb1e5ef81e..86ac4c89b46 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -87,7 +87,9 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python #[pyfunction] #[pyo3(pass_module)] - fn pyfunction_with_module<'py>(module: &Bound<'py, PyModule>) -> PyResult> { + fn pyfunction_with_module<'py>( + module: &Bound<'py, PyModule>, + ) -> PyResult> { module.name() } diff --git a/guide/src/memory.md b/guide/src/memory.md index b14ce4496ad..f40c4c6fd25 100644 --- a/guide/src/memory.md +++ b/guide/src/memory.md @@ -1,5 +1,15 @@ # Memory management +
+⚠️ Warning: API update in progress 🛠️ + +PyO3 0.21 has introduced a significant new API, termed the "Bound" API after the new smart pointer `Bound`. + +This section on memory management is heavily weighted towards the now-deprecated "GIL Refs" API, which suffered from the drawbacks detailed here as well as CPU overheads. + +See [the smart pointer types](./types.md#pyo3s-smart-pointers) for description on the new, simplified, memory model of the Bound API, which is built as a thin wrapper on Python reference counting. +
+ Rust and Python have very different notions of memory management. Rust has a strict memory model with concepts of ownership, borrowing, and lifetimes, where memory is freed at predictable points in program execution. Python has @@ -10,12 +20,12 @@ Memory in Python is freed eventually by the garbage collector, but not usually in a predictable way. PyO3 bridges the Rust and Python memory models with two different strategies for -accessing memory allocated on Python's heap from inside Rust. These are -GIL-bound, or "owned" references, and GIL-independent `Py` smart pointers. +accessing memory allocated on Python's heap from inside Rust. These are +GIL Refs such as `&'py PyAny`, and GIL-independent `Py` smart pointers. ## GIL-bound memory -PyO3's GIL-bound, "owned references" (`&PyAny` etc.) make PyO3 more ergonomic to +PyO3's GIL Refs such as `&'py PyAny` make PyO3 more ergonomic to use by ensuring that their lifetime can never be longer than the duration the Python GIL is held. This means that most of PyO3's API can assume the GIL is held. (If PyO3 could not assume this, every PyO3 API would need to take a @@ -27,7 +37,9 @@ very simple and easy-to-understand programs like this: # use pyo3::types::PyString; # fn main() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { - let hello = py.eval_bound("\"Hello World!\"", None, None)?.downcast_into::()?; + let hello = py + .eval_bound("\"Hello World!\"", None, None)? + .downcast_into::()?; println!("Python says: {}", hello); Ok(()) })?; @@ -48,7 +60,9 @@ of the time we don't have to think about this, but consider the following: # fn main() -> PyResult<()> { Python::with_gil(|py| -> PyResult<()> { for _ in 0..10 { - let hello = py.eval_bound("\"Hello World!\"", None, None)?.downcast_into::()?; + let hello = py + .eval_bound("\"Hello World!\"", None, None)? + .downcast_into::()?; println!("Python says: {}", hello); } // There are 10 copies of `hello` on Python's heap here. @@ -76,7 +90,9 @@ is to acquire and release the GIL with each iteration of the loop. # fn main() -> PyResult<()> { for _ in 0..10 { Python::with_gil(|py| -> PyResult<()> { - let hello = py.eval_bound("\"Hello World!\"", None, None)?.downcast_into::()?; + let hello = py + .eval_bound("\"Hello World!\"", None, None)? + .downcast_into::()?; println!("Python says: {}", hello); Ok(()) })?; // only one copy of `hello` at a time @@ -97,7 +113,9 @@ Python::with_gil(|py| -> PyResult<()> { for _ in 0..10 { let pool = unsafe { py.new_pool() }; let py = pool.python(); - let hello = py.eval_bound("\"Hello World!\"", None, None)?.downcast_into::()?; + let hello = py + .eval_bound("\"Hello World!\"", None, None)? + .downcast_into::()?; println!("Python says: {}", hello); } Ok(()) diff --git a/guide/src/migration.md b/guide/src/migration.md index f2c478da4a8..5af6eb2336c 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -190,7 +190,9 @@ struct PyClassAsyncIter { impl PyClassAsyncIter { fn __anext__(&mut self) -> PyClassAwaitable { self.number += 1; - PyClassAwaitable { number: self.number } + PyClassAwaitable { + number: self.number, + } } fn __aiter__(slf: Py) -> Py { @@ -312,7 +314,10 @@ Python::with_gil(|py| { // `b` is not in the dictionary assert!(dict.get_item("b").is_none()); // `dict` is not hashable, so this fails with a `TypeError` - assert!(dict.get_item_with_error(dict).unwrap_err().is_instance_of::(py)); + assert!(dict + .get_item_with_error(dict) + .unwrap_err() + .is_instance_of::(py)); }); # } ``` @@ -333,7 +338,10 @@ Python::with_gil(|py| -> PyResult<()> { // `b` is not in the dictionary assert!(dict.get_item("b")?.is_none()); // `dict` is not hashable, so this fails with a `TypeError` - assert!(dict.get_item(dict).unwrap_err().is_instance_of::(py)); + assert!(dict + .get_item(dict) + .unwrap_err() + .is_instance_of::(py)); Ok(()) }); diff --git a/guide/src/performance.md b/guide/src/performance.md index fe362bed953..3db23ddf01e 100644 --- a/guide/src/performance.md +++ b/guide/src/performance.md @@ -4,26 +4,26 @@ To achieve the best possible performance, it is useful to be aware of several tr ## `extract` versus `downcast` -Pythonic API implemented using PyO3 are often polymorphic, i.e. they will accept `&PyAny` and try to turn this into multiple more concrete types to which the requested operation is applied. This often leads to chains of calls to `extract`, e.g. +Pythonic API implemented using PyO3 are often polymorphic, i.e. they will accept `&Bound<'_, PyAny>` and try to turn this into multiple more concrete types to which the requested operation is applied. This often leads to chains of calls to `extract`, e.g. ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::{exceptions::PyTypeError, types::PyList}; -fn frobnicate_list(list: &PyList) -> PyResult<&PyAny> { +fn frobnicate_list<'py>(list: &Bound<'_, PyList>) -> PyResult> { todo!() } -fn frobnicate_vec(vec: Vec<&PyAny>) -> PyResult<&PyAny> { +fn frobnicate_vec<'py>(vec: Vec>) -> PyResult> { todo!() } #[pyfunction] -fn frobnicate(value: &PyAny) -> PyResult<&PyAny> { - if let Ok(list) = value.extract::<&PyList>() { - frobnicate_list(list) - } else if let Ok(vec) = value.extract::>() { +fn frobnicate<'py>(value: &Bound<'py, PyAny>) -> PyResult> { + if let Ok(list) = value.extract::>() { + frobnicate_list(&list) + } else if let Ok(vec) = value.extract::>>() { frobnicate_vec(vec) } else { Err(PyTypeError::new_err("Cannot frobnicate that type.")) @@ -37,15 +37,15 @@ This suboptimal as the `FromPyObject` trait requires `extract` to have a `Res # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::{exceptions::PyTypeError, types::PyList}; -# fn frobnicate_list(list: &PyList) -> PyResult<&PyAny> { todo!() } -# fn frobnicate_vec(vec: Vec<&PyAny>) -> PyResult<&PyAny> { todo!() } +# fn frobnicate_list<'py>(list: &Bound<'_, PyList>) -> PyResult> { todo!() } +# fn frobnicate_vec<'py>(vec: Vec>) -> PyResult> { todo!() } # #[pyfunction] -fn frobnicate(value: &PyAny) -> PyResult<&PyAny> { +fn frobnicate<'py>(value: &Bound<'py, PyAny>) -> PyResult> { // Use `downcast` instead of `extract` as turning `PyDowncastError` into `PyErr` is quite costly. if let Ok(list) = value.downcast::() { frobnicate_list(list) - } else if let Ok(vec) = value.extract::>() { + } else if let Ok(vec) = value.extract::>>() { frobnicate_vec(vec) } else { Err(PyTypeError::new_err("Cannot frobnicate that type.")) @@ -53,9 +53,9 @@ fn frobnicate(value: &PyAny) -> PyResult<&PyAny> { } ``` -## Access to GIL-bound reference implies access to GIL token +## Access to Bound implies access to GIL token -Calling `Python::with_gil` is effectively a no-op when the GIL is already held, but checking that this is the case still has a cost. If an existing GIL token can not be accessed, for example when implementing a pre-existing trait, but a GIL-bound reference is available, this cost can be avoided by exploiting that access to GIL-bound reference gives zero-cost access to a GIL token via `PyAny::py`. +Calling `Python::with_gil` is effectively a no-op when the GIL is already held, but checking that this is the case still has a cost. If an existing GIL token can not be accessed, for example when implementing a pre-existing trait, but a GIL-bound reference is available, this cost can be avoided by exploiting that access to GIL-bound reference gives zero-cost access to a GIL token via `Bound::py`. For example, instead of writing @@ -66,34 +66,32 @@ For example, instead of writing struct Foo(Py); -struct FooRef<'a>(&'a PyList); +struct FooBound<'py>(Bound<'py, PyList>); -impl PartialEq for FooRef<'_> { +impl PartialEq for FooBound<'_> { fn eq(&self, other: &Foo) -> bool { Python::with_gil(|py| { - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. - let len = other.0.as_ref(py).len(); + let len = other.0.bind(py).len(); self.0.len() == len }) } } ``` -use more efficient +use the more efficient ```rust # #![allow(dead_code)] # use pyo3::prelude::*; # use pyo3::types::PyList; # struct Foo(Py); -# struct FooRef<'a>(&'a PyList); +# struct FooBound<'py>(Bound<'py, PyList>); # -impl PartialEq for FooRef<'_> { +impl PartialEq for FooBound<'_> { fn eq(&self, other: &Foo) -> bool { // Access to `&'a PyAny` implies access to `Python<'a>`. let py = self.0.py(); - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. - let len = other.0.as_ref(py).len(); + let len = other.0.bind(py).len(); self.0.len() == len } } diff --git a/guide/src/python-from-rust/calling-existing-code.md b/guide/src/python-from-rust/calling-existing-code.md index 46a6b6ebbed..53051d4ce51 100644 --- a/guide/src/python-from-rust/calling-existing-code.md +++ b/guide/src/python-from-rust/calling-existing-code.md @@ -28,7 +28,7 @@ fn main() -> PyResult<()> { [`Python::eval`]({{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval) is a method to execute a [Python expression](https://docs.python.org/3.7/reference/expressions.html) -and return the evaluated value as a `&PyAny` object. +and return the evaluated value as a `Bound<'py, PyAny>` object. ```rust use pyo3::prelude::*; @@ -105,10 +105,7 @@ can be used to generate a Python module which can then be used just as if it was to this function! ```rust -use pyo3::{ - prelude::*, - types::IntoPyDict, -}; +use pyo3::{prelude::*, types::IntoPyDict}; # fn main() -> PyResult<()> { Python::with_gil(|py| { @@ -293,7 +290,10 @@ fn main() -> PyResult<()> { let path = Path::new("/usr/share/python_app"); let py_app = fs::read_to_string(path.join("app.py"))?; let from_python = Python::with_gil(|py| -> PyResult> { - let syspath = py.import_bound("sys")?.getattr("path")?.downcast_into::()?; + let syspath = py + .import_bound("sys")? + .getattr("path")? + .downcast_into::()?; syspath.insert(0, &path)?; let app: Py = PyModule::from_code_bound(py, &py_app, "", "")? .getattr("run")? @@ -357,7 +357,14 @@ class House(object): } Err(e) => { house - .call_method1("__exit__", (e.get_type_bound(py), e.value_bound(py), e.traceback_bound(py))) + .call_method1( + "__exit__", + ( + e.get_type_bound(py), + e.value_bound(py), + e.traceback_bound(py), + ), + ) .unwrap(); } } diff --git a/guide/src/trait-bounds.md b/guide/src/trait-bounds.md index b0eee80c80a..65842dee71f 100644 --- a/guide/src/trait-bounds.md +++ b/guide/src/trait-bounds.md @@ -66,6 +66,7 @@ The following wrapper will call the Python model from Rust, using a struct to ho ```rust # #![allow(dead_code)] use pyo3::prelude::*; +use pyo3::types::PyList; # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); @@ -81,12 +82,8 @@ impl Model for UserModel { fn set_variables(&mut self, var: &Vec) { println!("Rust calling Python to set the variables"); Python::with_gil(|py| { - let values: Vec = var.clone(); - let list: PyObject = values.into_py(py); - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. - let py_model = self.model.as_ref(py); - py_model - .call_method("set_variables", (list,), None) + self.model.bind(py) + .call_method("set_variables", (PyList::new_bound(py, var),), None) .unwrap(); }) } @@ -94,9 +91,8 @@ impl Model for UserModel { fn get_results(&self) -> Vec { println!("Rust calling Python to get the results"); Python::with_gil(|py| { - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. self.model - .as_ref(py) + .bind(py) .call_method("get_results", (), None) .unwrap() .extract() @@ -107,9 +103,8 @@ impl Model for UserModel { fn compute(&mut self) { println!("Rust calling Python to perform the computation"); Python::with_gil(|py| { - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. self.model - .as_ref(py) + .bind(py) .call_method("compute", (), None) .unwrap(); }) @@ -168,6 +163,7 @@ This wrapper will also perform the type conversions between Python and Rust. ```rust # #![allow(dead_code)] # use pyo3::prelude::*; +# use pyo3::types::PyList; # # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); @@ -184,12 +180,8 @@ This wrapper will also perform the type conversions between Python and Rust. # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); # Python::with_gil(|py| { -# let values: Vec = var.clone(); -# let list: PyObject = values.into_py(py); -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. -# let py_model = self.model.as_ref(py); -# py_model -# .call_method("set_variables", (list,), None) +# self.model.bind(py) +# .call_method("set_variables", (PyList::new_bound(py, var),), None) # .unwrap(); # }) # } @@ -197,9 +189,8 @@ This wrapper will also perform the type conversions between Python and Rust. # fn get_results(&self) -> Vec { # println!("Rust calling Python to get the results"); # Python::with_gil(|py| { -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. # self.model -# .as_ref(py) +# .bind(py) # .call_method("get_results", (), None) # .unwrap() # .extract() @@ -210,9 +201,8 @@ This wrapper will also perform the type conversions between Python and Rust. # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); # Python::with_gil(|py| { -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. # self.model -# .as_ref(py) +# .bind(py) # .call_method("compute", (), None) # .unwrap(); # }) @@ -340,6 +330,7 @@ We used in our `get_results` method the following call that performs the type co ```rust # #![allow(dead_code)] # use pyo3::prelude::*; +# use pyo3::types::PyList; # # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); @@ -355,10 +346,9 @@ We used in our `get_results` method the following call that performs the type co impl Model for UserModel { fn get_results(&self) -> Vec { println!("Rust calling Python to get the results"); - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. Python::with_gil(|py| { self.model - .as_ref(py) + .bind(py) .call_method("get_results", (), None) .unwrap() .extract() @@ -368,12 +358,8 @@ impl Model for UserModel { # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); # Python::with_gil(|py| { -# let values: Vec = var.clone(); -# let list: PyObject = values.into_py(py); -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. -# let py_model = self.model.as_ref(py); -# py_model -# .call_method("set_variables", (list,), None) +# self.model.bind(py) +# .call_method("set_variables", (PyList::new_bound(py, var),), None) # .unwrap(); # }) # } @@ -381,9 +367,8 @@ impl Model for UserModel { # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); # Python::with_gil(|py| { -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. # self.model -# .as_ref(py) +# .bind(py) # .call_method("compute", (), None) # .unwrap(); # }) @@ -396,6 +381,7 @@ Let's break it down in order to perform better error handling: ```rust # #![allow(dead_code)] # use pyo3::prelude::*; +# use pyo3::types::PyList; # # pub trait Model { # fn set_variables(&mut self, inputs: &Vec); @@ -412,10 +398,9 @@ impl Model for UserModel { fn get_results(&self) -> Vec { println!("Get results from Rust calling Python"); Python::with_gil(|py| { - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. - let py_result: &PyAny = self + let py_result: Bound<'_, PyAny> = self .model - .as_ref(py) + .bind(py) .call_method("get_results", (), None) .unwrap(); @@ -432,12 +417,8 @@ impl Model for UserModel { # fn set_variables(&mut self, var: &Vec) { # println!("Rust calling Python to set the variables"); # Python::with_gil(|py| { -# let values: Vec = var.clone(); -# let list: PyObject = values.into_py(py); -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. -# let py_model = self.model.as_ref(py); -# py_model -# .call_method("set_variables", (list,), None) +# let py_model = self.model.bind(py) +# .call_method("set_variables", (PyList::new_bound(py, var),), None) # .unwrap(); # }) # } @@ -445,9 +426,8 @@ impl Model for UserModel { # fn compute(&mut self) { # println!("Rust calling Python to perform the computation"); # Python::with_gil(|py| { -# #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. # self.model -# .as_ref(py) +# .bind(py) # .call_method("compute", (), None) # .unwrap(); # }) @@ -478,6 +458,7 @@ It is also required to make the struct public. ```rust # #![allow(dead_code)] use pyo3::prelude::*; +use pyo3::types::PyList; pub trait Model { fn set_variables(&mut self, var: &Vec); @@ -533,12 +514,9 @@ impl Model for UserModel { fn set_variables(&mut self, var: &Vec) { println!("Rust calling Python to set the variables"); Python::with_gil(|py| { - let values: Vec = var.clone(); - let list: PyObject = values.into_py(py); - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. - let py_model = self.model.as_ref(py); - py_model - .call_method("set_variables", (list,), None) + self.model + .bind(py) + .call_method("set_variables", (PyList::new_bound(py, var),), None) .unwrap(); }) } @@ -546,10 +524,9 @@ impl Model for UserModel { fn get_results(&self) -> Vec { println!("Get results from Rust calling Python"); Python::with_gil(|py| { - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. - let py_result: &PyAny = self + let py_result: Bound<'_, PyAny> = self .model - .as_ref(py) + .bind(py) .call_method("get_results", (), None) .unwrap(); @@ -567,9 +544,8 @@ impl Model for UserModel { fn compute(&mut self) { println!("Rust calling Python to perform the computation"); Python::with_gil(|py| { - #[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. self.model - .as_ref(py) + .bind(py) .call_method("compute", (), None) .unwrap(); }) diff --git a/guide/src/types.md b/guide/src/types.md index 9c4ed4dc849..2ba69f72669 100644 --- a/guide/src/types.md +++ b/guide/src/types.md @@ -65,8 +65,8 @@ use pyo3::types::PyList; fn example<'py>(py: Python<'py>) -> PyResult<()> { let x: Bound<'py, PyList> = PyList::empty_bound(py); x.append(1)?; - let y: Bound<'py, PyList> = x.clone(); // y is a new reference to the same list - drop(x); // release the original reference x + let y: Bound<'py, PyList> = x.clone(); // y is a new reference to the same list + drop(x); // release the original reference x Ok(()) } # Python::with_gil(example).unwrap(); @@ -107,7 +107,10 @@ The correct way to solve this is to add the `'py` lifetime as a parameter for th ```rust # use pyo3::prelude::*; -fn add<'py>(left: &Bound<'py, PyAny>, right: &Bound<'py, PyAny>) -> PyResult> { +fn add<'py>( + left: &Bound<'py, PyAny>, + right: &Bound<'py, PyAny>, +) -> PyResult> { left.add(right) } # Python::with_gil(|py| { @@ -177,8 +180,8 @@ Each concrete Python type such as `PyAny`, `PyTuple` and `PyDict` exposes its AP Each type's API is exposed as a trait: [`PyAnyMethods`], [`PyTupleMethods`], [`PyDictMethods`], and so on for all concrete types. Using traits rather than associated methods on the `Bound` smart pointer is done for a couple of reasons: - Clarity of documentation: each trait gets its own documentation page in the PyO3 API docs. If all methods were on the `Bound` smart pointer directly, the vast majority of PyO3's API would be on a single, extremely long, documentation page. -- Consistency: downstream code implementing [Rust APIs for existing Python types](#creating-a-rust-api-for-an-existing-python-type) can also follow this pattern of using a trait. Downstream code would not be allowed to add new associated methods directly on the `Bound` type. -- Future design: it is hoped that a future Rust with [arbitrary self types](TODO) will remove the need for these traits in favour of placing the methods directly on `PyAny`, `PyTuple`, `PyDict`, and so on. +- Consistency: downstream code implementing Rust APIs for existing Python types can also follow this pattern of using a trait. Downstream code would not be allowed to add new associated methods directly on the `Bound` type. +- Future design: it is hoped that a future Rust with [arbitrary self types](https://github.com/rust-lang/rust/issues/44874) will remove the need for these traits in favour of placing the methods directly on `PyAny`, `PyTuple`, `PyDict`, and so on. These traits are all included in the `pyo3::prelude` module, so with the glob import `use pyo3::prelude::*` the full PyO3 API is made available to downstream code. @@ -228,11 +231,11 @@ Custom [`#[pyclass]`][pyclass] types implement [`PyTypeCheck`], so `.downcast()` use pyo3::prelude::*; #[pyclass] -struct MyClass { } +struct MyClass {} # fn example<'py>(py: Python<'py>) -> PyResult<()> { // create a new Python `tuple`, and use `.into_any()` to erase the type -let obj: Bound<'py, PyAny> = Bound::new(py, MyClass { })?.into_any(); +let obj: Bound<'py, PyAny> = Bound::new(py, MyClass {})?.into_any(); // use `.downcast()` to cast to `MyClass` without transferring ownership let _: &Bound<'py, MyClass> = obj.downcast()?; @@ -291,7 +294,7 @@ a list: # use pyo3::prelude::*; # use pyo3::types::PyList; # Python::with_gil(|py| -> PyResult<()> { -#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API. +#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API. let obj: &PyAny = PyList::empty(py); // To &PyList with PyAny::downcast @@ -312,11 +315,11 @@ For a `&PyAny` object reference `any` where the underlying object is a `#[pyclas # use pyo3::prelude::*; # #[pyclass] #[derive(Clone)] struct MyClass { } # Python::with_gil(|py| -> PyResult<()> { -#[allow(deprecated)] // into_ref is part of the deprecated GIL Refs API +#[allow(deprecated)] // into_ref is part of the deprecated GIL Refs API let obj: &PyAny = Py::new(py, MyClass {})?.into_ref(py); // To &PyCell with PyAny::downcast -#[allow(deprecated)] // &PyCell is part of the deprecated GIL Refs API +#[allow(deprecated)] // &PyCell is part of the deprecated GIL Refs API let _: &PyCell = obj.downcast()?; // To Py (aka PyObject) with .into() @@ -351,7 +354,7 @@ To see all Python types exposed by `PyO3` consult the [`pyo3::types`][pyo3::type # use pyo3::prelude::*; # use pyo3::types::PyList; # Python::with_gil(|py| -> PyResult<()> { -#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API. +#[allow(deprecated)] // PyList::empty is part of the deprecated "GIL Refs" API. let list = PyList::empty(py); // Use methods from PyAny on all Python types with Deref implementation @@ -361,7 +364,7 @@ let _ = list.repr()?; let _: &PyAny = list; // To &PyAny explicitly with .as_ref() -#[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. +#[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. let _: &PyAny = list.as_ref(); // To Py with .into() or Py::from() @@ -401,7 +404,7 @@ Like PyO3's Python native types, the GIL Ref `&PyCell` implements `Deref PyResult<()> { -#[allow(deprecated)] // &PyCell is part of the deprecated GIL Refs API +#[allow(deprecated)] // &PyCell is part of the deprecated GIL Refs API let cell: &PyCell = PyCell::new(py, MyClass {})?; // To PyRef with .borrow() or .try_borrow() @@ -422,7 +425,7 @@ let _: &mut MyClass = &mut *py_ref_mut; # use pyo3::prelude::*; # #[pyclass] struct MyClass { } # Python::with_gil(|py| -> PyResult<()> { -#[allow(deprecated)] // &PyCell is part of the deprecate GIL Refs API +#[allow(deprecated)] // &PyCell is part of the deprecate GIL Refs API let cell: &PyCell = PyCell::new(py, MyClass {})?; // Use methods from PyAny on PyCell with Deref implementation @@ -432,7 +435,7 @@ let _ = cell.repr()?; let _: &PyAny = cell; // To &PyAny explicitly with .as_ref() -#[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. +#[allow(deprecated)] // as_ref is part of the deprecated "GIL Refs" API. let _: &PyAny = cell.as_ref(); # Ok(()) # }).unwrap(); @@ -451,7 +454,7 @@ let _: &PyAny = cell.as_ref(); [`PyTupleMethods`]: {{#PYO3_DOCS_URL}}/pyo3/types/trait.PyTupleMethods.html [pyclass]: class.md [Borrowed]: {{#PYO3_DOCS_URL}}/pyo3/struct.Borrowed.html -[Drop]: https://doc.rust-lang.org/std/drop/trait.Drop.html +[Drop]: https://doc.rust-lang.org/std/ops/trait.Drop.html [eval]: {{#PYO3_DOCS_URL}}/pyo3/marker/struct.Python.html#method.eval [clone_ref]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.clone_ref [pyo3::types]: {{#PYO3_DOCS_URL}}/pyo3/types/index.html diff --git a/noxfile.py b/noxfile.py index 5f7ca8c04f8..f70fced76d6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -372,7 +372,6 @@ def docs(session: nox.Session) -> None: "--no-deps", "--workspace", *cargo_flags, - *session.posargs, )