Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 28 additions & 32 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,17 +314,17 @@ struct MyClass {
Python::attach(|py| {
let obj = Bound::new(py, MyClass { num: 3 }).unwrap();
{
let obj_ref = obj.borrow(); // Get PyRef
let obj_ref = obj.try_borrow_guard().unwrap(); // Get PyClassGuard
assert_eq!(obj_ref.num, 3);
// You cannot get PyRefMut unless all PyRefs are dropped
assert!(obj.try_borrow_mut().is_err());
// You cannot get PyClassGuardMut unless all PyClassGuards are dropped
assert!(obj.try_borrow_guard_mut().is_err());
}
{
let mut obj_mut = obj.borrow_mut(); // Get PyRefMut
let mut obj_mut = obj.try_borrow_guard_mut().unwrap(); // Get PyClassGuardMut
obj_mut.num = 5;
// You cannot get any other refs until the PyRefMut is dropped
assert!(obj.try_borrow().is_err());
assert!(obj.try_borrow_mut().is_err());
assert!(obj.try_borrow_guard().is_err());
assert!(obj.try_borrow_guard_mut().is_err());
}

// You can convert `Bound` to a Python object
Expand All @@ -334,7 +334,6 @@ Python::attach(|py| {

A `Bound<'py, T>` is restricted to the Python lifetime `'py`.
To make the object longer lived (for example, to store it in a struct on the Rust side), use `Py<T>`.
`Py<T>` needs a `Python<'_>` token to allow access:

```rust
# use pyo3::prelude::*;
Expand All @@ -349,11 +348,8 @@ fn return_myclass() -> Py<MyClass> {

let obj = return_myclass();

Python::attach(move |py| {
let bound = obj.bind(py); // Py<MyClass>::bind returns &Bound<'py, MyClass>
let obj_ref = bound.borrow(); // Get PyRef<T>
assert_eq!(obj_ref.num, 1);
});
let obj_ref = obj.try_borrow_guard().unwrap(); // Get PyClassGuard<T>
assert_eq!(obj_ref.num, 1);
```

### frozen classes: Opting out of interior mutability
Expand Down Expand Up @@ -415,9 +411,8 @@ Currently, only classes defined in Rust and builtins provided by PyO3 can be inh

To initialize a class, which inherits from another class, use the `PyClassInitializer` API.

To get a parent class from a child, use [`PyRef`] instead of `&self` for methods, or [`PyRefMut`] instead of `&mut self`.
Then you can access a parent class by `self_.as_super()` as `&PyRef<Self::BaseClass>`, or by `self_.into_super()` as `PyRef<Self::BaseClass>` (and similar for the `PyRefMut` case).
For convenience, `self_.as_ref()` can also be used to get `&Self::BaseClass` directly; however, this approach does not let you access base classes higher in the inheritance hierarchy, for which you would need to chain multiple `as_super` or `into_super` calls.
To get a parent class from a child, use [`PyClassGuard`] instead of `&self` for methods, or [`PyClassGuardMut`] instead of `&mut self`.
Then you can access a parent class by `self_.as_super()` as `&PyClassGuard<Self::BaseClass>`, or by `self_.into_super()` as `PyClassGuard<Self::BaseClass>` (and similar for the `PyRefMut` case).

```rust
# use pyo3::prelude::*;
Expand Down Expand Up @@ -452,8 +447,8 @@ impl SubClass {
.add_subclass(SubClass { val2: 15 })
}

fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {
let super_ = self_.as_super(); // Get &PyRef<BaseClass>
fn method2(self_: PyClassGuard<'_, Self>) -> PyResult<usize> {
let super_ = self_.as_super(); // Get &PyClassGuard<BaseClass>
super_.method1().map(|x| x * self_.val2)
}
}
Expand All @@ -470,24 +465,24 @@ impl SubSubClass {
PyClassInitializer::from(SubClass::new()).add_subclass(SubSubClass { val3: 20 })
}

fn method3(self_: PyRef<'_, Self>) -> PyResult<usize> {
let base = self_.as_super().as_super(); // Get &PyRef<'_, BaseClass>
fn method3(self_: PyClassGuard<'_, Self>) -> PyResult<usize> {
let base = self_.as_super().as_super(); // Get &PyClassGuard<'_, BaseClass>
base.method1().map(|x| x * self_.val3)
}

fn method4(self_: PyRef<'_, Self>) -> PyResult<usize> {
fn method4(self_: PyClassGuard<'_, Self>) -> PyResult<usize> {
let v = self_.val3;
let super_ = self_.into_super(); // Get PyRef<'_, SubClass>
let super_ = self_.into_super(); // Get PyClassGuard<'_, SubClass>
SubClass::method2(super_).map(|x| x * v)
}

fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) {
fn get_values(self_: PyClassGuard<'_, Self>) -> (usize, usize, usize) {
let val1 = self_.as_super().as_super().val1;
let val2 = self_.as_super().val2;
(val1, val2, self_.val3)
}

fn double_values(mut self_: PyRefMut<'_, Self>) {
fn double_values(mut self_: PyClassGuardMut<'_, Self>) {
self_.as_super().as_super().val1 *= 2;
self_.as_super().val2 *= 2;
self_.val3 *= 2;
Expand Down Expand Up @@ -526,7 +521,7 @@ You can inherit native types such as `PyDict`, if they implement [`PySizedLayout
This is not supported when building for the Python limited API (aka the `abi3` feature of PyO3).

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.cast::<BaseClass>()`.
To access the Rust fields use `slf.try_borrow_guard()` or `slf.try_borrow_guard_mut()`, and to access the base class use `slf.cast::<BaseClass>()`.

```rust
# #[cfg(any(not(Py_LIMITED_API), Py_3_12))] {
Expand All @@ -548,7 +543,7 @@ impl DictWithCounter {
}

fn set(slf: &Bound<'_, Self>, key: String, value: Bound<'_, PyAny>) -> PyResult<()> {
slf.borrow_mut().counter.entry(key.clone()).or_insert(0);
slf.try_borrow_guard_mut()?.counter.entry(key.clone()).or_insert(0);
let dict = slf.cast::<PyDict>()?;
dict.set_item(key, value)
}
Expand Down Expand Up @@ -940,7 +935,7 @@ Class objects can be used as arguments to `#[pyfunction]`s and `#[pymethods]` in

- `Py<T>` or `Bound<'py, T>` smart pointers to the class Python object,
- `&T` or `&mut T` references to the Rust data contained in the Python object, or
- `PyRef<T>` and `PyRefMut<T>` reference wrappers.
- `PyClassGuard<T>` and `PyClassGuardMut<T>` reference wrappers.

Examples of each of these below:

Expand All @@ -961,17 +956,18 @@ fn increment_field(my_class: &mut MyClass) {
// Take a reference wrapper when borrowing should be automatic,
// but access to the Python object is still needed
#[pyfunction]
fn print_field_and_return_me(my_class: PyRef<'_, MyClass>) -> PyRef<'_, MyClass> {
fn print_field_and_return_me(my_class: PyClassGuard<'_, MyClass>) -> PyClassGuard<'_, MyClass> {
println!("{}", my_class.my_field);
my_class
}

// Take (a reference to) a Python object smart pointer when borrowing needs to be managed manually.
#[pyfunction]
fn increment_then_print_field(my_class: &Bound<'_, MyClass>) {
my_class.borrow_mut().my_field += 1;
fn increment_then_print_field(my_class: &Bound<'_, MyClass>) -> PyResult<()> {
my_class.try_borrow_guard_mut()?.my_field += 1;

println!("{}", my_class.borrow().my_field);
println!("{}", my_class.try_borrow_guard()?.my_field);
Ok(())
}

// When the Python object smart pointer needs to be stored elsewhere prefer `Py<T>` over `Bound<'py, T>`
Expand Down Expand Up @@ -1575,8 +1571,8 @@ impl pyo3::impl_::pyclass::PyClassImpl for MyClass {
[`Py<T>`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Py.html
[`Bound<'py, T>`]: {{#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
[`PyClassGuard`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/struct.PyClassGuard.html
[`PyClassGuardMut`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/struct.PyClassGuardMut.html
[`PyClassInitializer<T>`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass_init/struct.PyClassInitializer.html

[`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
Expand Down
4 changes: 2 additions & 2 deletions guide/src/class/numeric.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ impl Number {
#
#[pymethods]
impl Number {
fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __pos__(slf: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
slf
}

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

fn __str__(&self) -> String {
Expand Down
4 changes: 2 additions & 2 deletions guide/src/class/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ impl Number {
// This is the equivalent of `self.__class__.__name__` in Python.
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
// To access fields of the Rust struct, we need to borrow from the Bound object.
Ok(format!("{}({})", class_name, slf.borrow().0))
Ok(format!("{}({})", class_name, slf.try_borrow_guard()?.0))
}
}
```
Expand Down Expand Up @@ -351,7 +351,7 @@ impl Number {

fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
let class_name: Bound<'_, PyString> = slf.get_type().qualname()?;
Ok(format!("{}({})", class_name, slf.borrow().0))
Ok(format!("{}({})", class_name, slf.try_borrow_guard()?.0))
}

fn __str__(&self) -> String {
Expand Down
14 changes: 7 additions & 7 deletions guide/src/class/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The following sections list all magic methods for which PyO3 implements the nece
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 `Bound` reference like `self_: PyRef<'_, Self>` and `self_: PyRefMut<'_, Self>`, as described [in the parent section](../class.md#inheritance).
It can be `&self`, `&mut self` or a `Bound` reference like `self_: PyClassGuard<'_, Self>` and `self_: PyClassGuardMut<'_, Self>`, as described [in the parent section](../class.md#inheritance).
- An optional `Python<'py>` argument is always allowed as the first argument.
- Return values can be optionally wrapped in `PyResult`.
- `object` means that any type is allowed that can be extracted from a Python
Expand Down Expand Up @@ -196,10 +196,10 @@ struct MyIterator {

#[pymethods]
impl MyIterator {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __iter__(slf: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
slf
}
fn __next__(slf: PyRefMut<'_, Self>) -> Option<Py<PyAny>> {
fn __next__(slf: PyClassGuardMut<'_, Self>) -> Option<Py<PyAny>> {
slf.iter.lock().unwrap().next()
}
}
Expand All @@ -219,11 +219,11 @@ struct Iter {

#[pymethods]
impl Iter {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __iter__(slf: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
slf
}

fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<usize> {
fn __next__(mut slf: PyClassGuardMut<'_, Self>) -> Option<usize> {
slf.inner.next()
}
}
Expand All @@ -235,11 +235,11 @@ struct Container {

#[pymethods]
impl Container {
fn __iter__(slf: PyRef<'_, Self>) -> PyResult<Py<Iter>> {
fn __iter__(slf: PyClassGuard<'_, Self>, py: Python<'_>) -> PyResult<Py<Iter>> {
let iter = Iter {
inner: slf.iter.clone().into_iter(),
};
Py::new(slf.py(), iter)
Py::new(py, iter)
}
}

Expand Down
8 changes: 4 additions & 4 deletions guide/src/conversions/tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ It is also worth remembering the following special types:
| `Python<'py>` | A token used to prove attachment to the Python interpreter. |
| `Bound<'py, T>` | A Python object with a lifetime which binds it to the attachment to the Python interpreter. This provides access to most of PyO3's APIs. |
| `Py<T>` | A Python object not connected to any lifetime of attachment to the Python interpreter. This can be sent to other threads. |
| `PyRef<T>` | A `#[pyclass]` borrowed immutably. |
| `PyRefMut<T>` | A `#[pyclass]` borrowed mutably. |
| `PyClassGuard<T>` | A `#[pyclass]` borrowed immutably. |
| `PyClassGuardMut<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).

Expand Down Expand Up @@ -105,8 +105,8 @@ Finally, the following Rust types are also able to convert to Python as return v
| `BTreeSet<T>` | `Set[T]` |
| `Py<T>` | `T` |
| `Bound<T>` | `T` |
| `PyRef<T: PyClass>` | `T` |
| `PyRefMut<T: PyClass>` | `T` |
| `PyClassGuard<T: PyClass>` | `T` |
| `PyClassGuardMut<T: PyClass>` | `T` |

[^1]: Requires the `num-bigint` optional feature.

Expand Down
8 changes: 4 additions & 4 deletions guide/src/conversions/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ let v: Vec<i32> = list.extract()?;
This method is available for many Python object types, and can produce a wide variety of Rust types, which you can check out in the implementor list of [`FromPyObject`].

[`FromPyObject`] is also implemented for your own Rust types wrapped as Python objects (see [the chapter about classes](../class.md)).
There, in order to both be able to operate on mutable references *and* satisfy Rust's rules of non-aliasing mutable references, you have to extract the PyO3 reference wrappers [`PyRef`] and [`PyRefMut`].
There, in order to both be able to operate on mutable references *and* satisfy Rust's rules of non-aliasing mutable references, you have to extract the PyO3 reference wrappers [`PyClassGuard`] and [`PyClassGuardMut`].
They work like the reference wrappers of `std::cell::RefCell` and ensure (at runtime) that Rust borrows are allowed.

### Deriving [`FromPyObject`]
Expand Down Expand Up @@ -537,7 +537,7 @@ impl<'py> FromPyObject<'_, 'py> for Number {

fn extract(obj: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result<Self, Self::Error> {
if let Ok(obj) = obj.cast::<Self>() { // first try extraction via class object
Ok(obj.borrow().clone())
Ok(obj.try_borrow_guard()?.clone())
} else {
obj.extract::<i32>().map(Self) // otherwise try integer directly
}
Expand Down Expand Up @@ -756,8 +756,8 @@ In the example above we used `BoundObject::into_any` and `BoundObject::unbind` t
[`IntoPyObject`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObject.html
[`IntoPyObjectExt`]: {{#PYO3_DOCS_URL}}/pyo3/conversion/trait.IntoPyObjectExt.html

[`PyRef`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRef.html
[`PyRefMut`]: {{#PYO3_DOCS_URL}}/pyo3/pycell/struct.PyRefMut.html
[`PyClassGuard`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/struct.PyClassGuard.html
[`PyClassGuardMut`]: {{#PYO3_DOCS_URL}}/pyo3/pyclass/struct.PyClassGuardMut.html
[`BoundObject`]: {{#PYO3_DOCS_URL}}/pyo3/instance/trait.BoundObject.html

[`Result`]: https://doc.rust-lang.org/stable/std/result/enum.Result.html
4 changes: 1 addition & 3 deletions guide/src/parallelism.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ let allowed_ids: Vec<bool> = Python::attach(|outer_py| {
let instances: Vec<Py<UserID>> = (0..10).map(|x| Py::new(outer_py, UserID { id: x }).unwrap()).collect();
outer_py.detach(|| {
instances.par_iter().map(|instance| {
Python::attach(|inner_py| {
instance.borrow(inner_py).id > 5
})
instance.try_borrow_guard().unwrap().id > 5
}).collect()
})
});
Expand Down
1 change: 1 addition & 0 deletions newsfragments/6120.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
deprecate `PyRef` and `PyRefMut` in favor of `PyClassGuard` and `PyClassGuardMut` respectively
2 changes: 2 additions & 0 deletions newsfragments/6121.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
added `Bound::try_borrow_guard(_mut)`
added `Py::try_borrow_guard(_mut)`
2 changes: 2 additions & 0 deletions newsfragments/6121.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
deprecated `Bound::(try_)borrow(_mut)`
deprecated `Py::(try_)borrow(_mut)`
30 changes: 15 additions & 15 deletions pytests/src/awaitable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ pub mod awaitable {
}
}

fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __await__(pyself: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
pyself
}

fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __iter__(pyself: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
pyself
}

Expand Down Expand Up @@ -79,15 +79,15 @@ pub mod awaitable {
}
}

fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __await__(pyself: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
pyself
}

fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __iter__(pyself: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
pyself
}

fn __next__(mut pyself: PyRefMut<'_, Self>) -> PyResult<PyRefMut<'_, Self>> {
fn __next__(mut pyself: PyClassGuardMut<'_, Self>) -> PyResult<PyClassGuardMut<'_, Self>> {
match pyself.result {
Some(_) => match pyself.result.take().unwrap() {
Ok(v) => Err(PyStopIteration::new_err(v)),
Expand All @@ -97,20 +97,20 @@ pub mod awaitable {
}
}

fn send<'py>(
pyself: PyRefMut<'py, Self>,
_value: Bound<'py, PyAny>,
) -> PyResult<PyRefMut<'py, Self>> {
fn send<'a>(
pyself: PyClassGuardMut<'a, Self>,
_value: Bound<'_, PyAny>,
) -> PyResult<PyClassGuardMut<'a, Self>> {
Self::__next__(pyself)
}

#[pyo3(signature = (_value, _a = None, _b = None))]
fn throw<'py>(
pyself: PyRefMut<'py, Self>,
_value: Bound<'py, PyAny>,
_a: Option<Bound<'py, PyAny>>,
_b: Option<Bound<'py, PyAny>>,
) -> PyResult<PyRefMut<'py, Self>> {
fn throw<'a>(
pyself: PyClassGuardMut<'a, Self>,
_value: Bound<'_, PyAny>,
_a: Option<Bound<'_, PyAny>>,
_b: Option<Bound<'_, PyAny>>,
) -> PyResult<PyClassGuardMut<'a, Self>> {
Self::__next__(pyself)
}

Expand Down
4 changes: 2 additions & 2 deletions pytests/src/pyclasses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ impl Number {
Self(self.0 ^ other.0)
}

fn __pos__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __pos__(slf: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
slf
}

Expand All @@ -309,7 +309,7 @@ impl Number {
}
}

fn __abs__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
fn __abs__(slf: PyClassGuard<'_, Self>) -> PyClassGuard<'_, Self> {
slf
}

Expand Down
Loading
Loading