Skip to content

Commit

Permalink
docs: update Python classes section of the guide
Browse files Browse the repository at this point in the history
  • Loading branch information
Icxolu committed Feb 28, 2024
1 parent a15e4b1 commit c5dd57e
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 53 deletions.
6 changes: 3 additions & 3 deletions examples/decorator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ impl PyCounter {
fn __call__(
&self,
py: Python<'_>,
args: &PyTuple,
kwargs: Option<Bound<'_, PyDict>>,
args: &Bound<'_, PyTuple>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
let old_count = self.count.get();
let new_count = old_count + 1;
Expand All @@ -51,7 +51,7 @@ impl PyCounter {
println!("{} has been called {} time(s).", name, new_count);

// After doing something, we finally forward the call to the wrapped function
let ret = self.wraps.call_bound(py, args, kwargs.as_ref())?;
let ret = self.wraps.call_bound(py, args, kwargs)?;

// We could do something with the return value of
// the function before returning it
Expand Down
68 changes: 34 additions & 34 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,32 +181,29 @@ The next step is to create the module initializer and add our class to it:
# struct Number(i32);
#
#[pymodule]
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
fn my_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
```

## PyCell and interior mutability
## Bound and interior mutability

You sometimes need to convert your `pyclass` into a Python object and access it
You sometimes need to convert your `#[pyclass]` into a Python object and access it
from Rust code (e.g., for testing it).
[`PyCell`] is the primary interface for that.
[`Bound`] is the primary interface for that.

`PyCell<T: PyClass>` is always allocated in the Python heap, so Rust doesn't have ownership of it.
In other words, Rust code can only extract a `&PyCell<T>`, not a `PyCell<T>`.

Thus, to mutate data behind `&PyCell` safely, PyO3 employs the
To mutate data behind a `Bound<'_, T>` safely, PyO3 employs the
[Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html)
like [`RefCell`].

Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`.
Users who are familiar with `RefCell` can use `Bound` just like `RefCell`.

For users who are not very familiar with `RefCell`, here is a reminder of Rust's rules of borrowing:
- At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.
- References must always be valid.

`PyCell`, like `RefCell`, ensures these borrowing rules by tracking references at runtime.
`Bound`, like `RefCell`, ensures these borrowing rules by tracking references at runtime.

```rust
# use pyo3::prelude::*;
Expand All @@ -216,8 +213,7 @@ struct MyClass {
num: i32,
}
Python::with_gil(|py| {
# #[allow(deprecated)]
let obj = PyCell::new(py, MyClass { num: 3 }).unwrap();
let obj = Bound::new(py, MyClass { num: 3 }).unwrap();
{
let obj_ref = obj.borrow(); // Get PyRef
assert_eq!(obj_ref.num, 3);
Expand All @@ -232,12 +228,12 @@ Python::with_gil(|py| {
assert!(obj.try_borrow_mut().is_err());
}

// You can convert `&PyCell` to a Python object
// You can convert `Bound` to a Python object
pyo3::py_run!(py, obj, "assert obj.num == 5");
});
```

`&PyCell<T>` is bounded by the same lifetime as a [`GILGuard`].
A `Bound<'py, T>` is restricted to the GIL lifetime `'py`.
To make the object longer lived (for example, to store it in a struct on the
Rust side), you can use `Py<T>`, which stores an object longer than the GIL
lifetime, and therefore needs a `Python<'_>` token to access.
Expand All @@ -256,8 +252,8 @@ fn return_myclass() -> Py<MyClass> {
let obj = return_myclass();

Python::with_gil(|py| {
let cell = obj.as_ref(py); // Py<MyClass>::as_ref returns &PyCell<MyClass>
let obj_ref = cell.borrow(); // Get PyRef<T>
let bound = obj.bind(py); // Py<MyClass>::bind returns &Bound<'_, MyClass>
let obj_ref = bound.borrow(); // Get PyRef<T>
assert_eq!(obj_ref.num, 1);
});
```
Expand All @@ -266,7 +262,7 @@ Python::with_gil(|py| {

As detailed above, runtime borrow checking is currently enabled by default. But a class can opt of out it by declaring itself `frozen`. It can still use interior mutability via standard Rust types like `RefCell` or `Mutex`, but it is not bound to the implementation provided by PyO3 and can choose the most appropriate strategy on field-by-field basis.

Classes which are `frozen` and also `Sync`, e.g. they do use `Mutex` but not `RefCell`, can be accessed without needing the Python GIL via the `PyCell::get` and `Py::get` methods:
Classes which are `frozen` and also `Sync`, e.g. they do use `Mutex` but not `RefCell`, can be accessed without needing the Python GIL via the `Bound::get` and `Py::get` methods:

```rust
use std::sync::atomic::{AtomicUsize, Ordering};
Expand Down Expand Up @@ -412,8 +408,9 @@ You can inherit native types such as `PyDict`, if they implement
[`PySizedLayout`]({{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PySizedLayout.html).
This is not supported when building for the Python limited API (aka the `abi3` feature of PyO3).

However, because of some technical problems, we don't currently provide safe upcasting methods for types
that inherit native types. Even in such cases, you can unsafely get a base class by raw pointer conversion.
To convert between the Rust type and its native base class, you can take
`slf` as a Python object. To access the Rust fields use `slf.borrow()` or
`slf.borrow_mut()`, and to access the base class use `slf.downcast::<BaseClass>()`.

```rust
# #[cfg(not(Py_LIMITED_API))] {
Expand All @@ -434,10 +431,9 @@ impl DictWithCounter {
Self::default()
}

fn set(mut self_: PyRefMut<'_, Self>, key: String, value: &PyAny) -> PyResult<()> {
self_.counter.entry(key.clone()).or_insert(0);
let py = self_.py();
let dict: &PyDict = unsafe { py.from_borrowed_ptr_or_err(self_.as_ptr())? };
fn set(slf: &Bound<'_, Self>, key: String, value: &PyAny) -> PyResult<()> {
slf.borrow_mut().counter.entry(key.clone()).or_insert(0);
let dict = slf.downcast::<PyDict>()?;
dict.set_item(key, value)
}
}
Expand Down Expand Up @@ -491,7 +487,7 @@ struct MyDict {
impl MyDict {
#[new]
#[pyo3(signature = (*args, **kwargs))]
fn new(args: &PyAny, kwargs: Option<&PyAny>) -> Self {
fn new(args: &Bound<'_, PyAny>, kwargs: Option<&Bound<'_, PyAny>>) -> Self {
Self { private: 0 }
}

Expand Down Expand Up @@ -702,7 +698,7 @@ Declares a class method callable from Python.

* The first parameter is the type object of the class on which the method is called.
This may be the type object of a derived class.
* The first parameter implicitly has type `&PyType`.
* The first parameter implicitly has type `&Bound<'_, PyType>`.
* For details on `parameter-list`, see the documentation of `Method arguments` section.
* The return type must be `PyResult<T>` or `T` for some `T` that implements `IntoPy<PyObject>`.

Expand Down Expand Up @@ -802,23 +798,23 @@ struct MyClass {
my_field: i32,
}

// Take a GIL-bound reference when the underlying `PyCell` is irrelevant.
// Take a GIL-bound reference when the underlying `Bound` is irrelevant.
#[pyfunction]
fn increment_field(my_class: &mut MyClass) {
my_class.my_field += 1;
}

// Take a GIL-bound reference wrapper when borrowing should be automatic,
// but interaction with the underlying `PyCell` is desired.
// but interaction with the underlying `Bound` is desired.
#[pyfunction]
fn print_field(my_class: PyRef<'_, MyClass>) {
println!("{}", my_class.my_field);
}

// Take a GIL-bound reference to the underlying cell
// Take a GIL-bound reference to the underlying Bound
// when borrowing needs to be managed manually.
#[pyfunction]
fn increment_then_print_field(my_class: &PyCell<MyClass>) {
fn increment_then_print_field(my_class: &Bound<'_, MyClass>) {
my_class.borrow_mut().my_field += 1;

println!("{}", my_class.borrow().my_field);
Expand Down Expand Up @@ -877,9 +873,9 @@ impl MyClass {
fn method(
&mut self,
num: i32,
py_args: &PyTuple,
py_args: &Bound<'_, PyTuple>,
name: &str,
py_kwargs: Option<&PyDict>,
py_kwargs: Option<&Bound<'_, PyDict>>,
) -> String {
let num_before = self.num;
self.num = num;
Expand Down Expand Up @@ -1231,6 +1227,9 @@ struct MyClass {
# #[allow(dead_code)]
num: i32,
}

impl pyo3::types::DerefToPyAny for MyClass {}

unsafe impl pyo3::type_object::HasPyGilRef for MyClass {
type AsRefTarget = pyo3::PyCell<Self>;
}
Expand Down Expand Up @@ -1278,6 +1277,8 @@ impl pyo3::IntoPy<PyObject> for MyClass {
impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
const IS_BASETYPE: bool = false;
const IS_SUBCLASS: bool = false;
const IS_MAPPING: bool = false;
const IS_SEQUENCE: bool = false;
type BaseType = PyAny;
type ThreadChecker = pyo3::impl_::pyclass::SendablePyClass<MyClass>;
type PyClassMutability = <<pyo3::PyAny as pyo3::impl_::pyclass::PyClassBaseType>::PyClassMutability as pyo3::impl_::pycell::PyClassMutability>::MutableChild;
Expand All @@ -1303,7 +1304,7 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
static DOC: pyo3::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = pyo3::sync::GILOnceCell::new();
DOC.get_or_try_init(py, || {
let collector = PyClassImplCollector::<Self>::new();
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "", None.or_else(|| collector.new_text_signature()))
build_pyclass_doc(<MyClass as pyo3::PyTypeInfo>::NAME, "\0", collector.new_text_signature())
}).map(::std::ops::Deref::deref)
}
}
Expand All @@ -1316,11 +1317,10 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
```


[`GILGuard`]: {{#PYO3_DOCS_URL}}/pyo3/struct.GILGuard.html
[`PyTypeInfo`]: {{#PYO3_DOCS_URL}}/pyo3/type_object/trait.PyTypeInfo.html

[`Py`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html
[`PyCell`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyCell.html
[`Bound`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Bound.html
[`PyClass`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/trait.PyClass.html
[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html
Expand Down
4 changes: 2 additions & 2 deletions guide/src/class/call.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ A [previous implementation] used a normal `u64`, which meant it required a `&mut
fn __call__(
&mut self,
py: Python<'_>,
args: &PyTuple,
kwargs: Option<&PyDict>,
args: &Bound<'_, PyTuple>,
kwargs: Option<&Bound<'_, PyDict>>,
) -> PyResult<Py<PyAny>> {
self.count += 1;
let name = self.wraps.getattr(py, "__name__")?;
Expand Down
16 changes: 8 additions & 8 deletions guide/src/class/numeric.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ and cast it to an `i32`.
# #![allow(dead_code)]
use pyo3::prelude::*;

fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
// 👇 This intentionally overflows!
Expand All @@ -48,7 +48,7 @@ We also add documentation, via `///` comments, which are visible to Python users
# #![allow(dead_code)]
use pyo3::prelude::*;

fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
Ok(val as i32)
Expand Down Expand Up @@ -212,7 +212,7 @@ use pyo3::prelude::*;
use pyo3::class::basic::CompareOp;
use pyo3::types::PyComplex;

fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
fn wrap(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
let val = obj.call_method1("__and__", (0xFFFFFFFF_u32,))?;
let val: u32 = val.extract()?;
Ok(val as i32)
Expand All @@ -229,7 +229,7 @@ impl Number {
Self(value)
}

fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
// Get the class name dynamically in case `Number` is subclassed
let class_name: String = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0))
Expand Down Expand Up @@ -326,7 +326,7 @@ impl Number {
}

#[pymodule]
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
fn my_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
Expand Down Expand Up @@ -411,8 +411,8 @@ the contracts of this function. Let's review those contracts:
- The GIL must be held. If it's not, calling this function causes a data race.
- The pointer must be valid, i.e. it must be properly aligned and point to a valid Python object.
Let's create that helper function. The signature has to be `fn(&PyAny) -> PyResult<T>`.
- `&PyAny` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
Let's create that helper function. The signature has to be `fn(&Bound<'_, PyAny>) -> PyResult<T>`.
- `&Bound<'_, PyAny>` represents a checked borrowed reference, so the pointer derived from it is valid (and not null).
- Whenever we have borrowed references to Python objects in scope, it is guaranteed that the GIL is held. This reference is also where we can get a [`Python`] token to use in our call to [`PyErr::take`].
```rust
Expand All @@ -421,7 +421,7 @@ use std::os::raw::c_ulong;
use pyo3::prelude::*;
use pyo3::ffi;
fn wrap(obj: &PyAny) -> Result<i32, PyErr> {
fn wrap(obj: &Bound<'_, PyAny>) -> Result<i32, PyErr> {
let py: Python<'_> = obj.py();
unsafe {
Expand Down
10 changes: 5 additions & 5 deletions guide/src/class/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Number {
}

#[pymodule]
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
fn my_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
Expand Down Expand Up @@ -76,7 +76,7 @@ In the `__repr__`, we used a hard-coded class name. This is sometimes not ideal,
because if the class is subclassed in Python, we would like the repr to reflect
the subclass name. This is typically done in Python code by accessing
`self.__class__.__name__`. In order to be able to access the Python type information
*and* the Rust struct, we need to use a `PyCell` as the `self` argument.
*and* the Rust struct, we need to use a `Bound` as the `self` argument.

```rust
# use pyo3::prelude::*;
Expand All @@ -86,7 +86,7 @@ the subclass name. This is typically done in Python code by accessing
#
#[pymethods]
impl Number {
fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
// This is the equivalent of `self.__class__.__name__` in Python.
let class_name: String = slf.get_type().qualname()?;
// To access fields of the Rust struct, we need to borrow the `PyCell`.
Expand Down Expand Up @@ -263,7 +263,7 @@ impl Number {
Self(value)
}

fn __repr__(slf: &PyCell<Self>) -> PyResult<String> {
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
let class_name: String = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0))
}
Expand Down Expand Up @@ -295,7 +295,7 @@ impl Number {
}

#[pymodule]
fn my_module(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
fn my_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Number>()?;
Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion guide/src/class/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ When PyO3 handles a magic method, a couple of changes apply compared to other `#
The following sections list of all magic methods PyO3 currently handles. The
given signatures should be interpreted as follows:
- All methods take a receiver as first argument, shown as `<self>`. It can be
`&self`, `&mut self` or a `PyCell` reference like `self_: PyRef<'_, Self>` and
`&self`, `&mut self` or a `Bound` reference like `self_: PyRef<'_, Self>` and
`self_: PyRefMut<'_, Self>`, as described [here](../class.md#inheritance).
- An optional `Python<'py>` argument is always allowed as the first argument.
- Return values can be optionally wrapped in `PyResult`.
Expand Down
6 changes: 6 additions & 0 deletions src/types/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,12 @@ impl IntoPy<Py<PyTuple>> for Bound<'_, PyTuple> {
}
}

impl IntoPy<Py<PyTuple>> for &'_ Bound<'_, PyTuple> {
fn into_py(self, _: Python<'_>) -> Py<PyTuple> {
self.clone().unbind()
}
}

#[cold]
fn wrong_tuple_length(t: &Bound<'_, PyTuple>, expected_length: usize) -> PyErr {
let msg = format!(
Expand Down

0 comments on commit c5dd57e

Please sign in to comment.