Merge pull request #3792 from davidhewitt/bound-pyclass-2

pyclass methods for `Bound`
This commit is contained in:
David Hewitt 2024-02-05 10:38:30 +00:00 committed by GitHub
commit 02f1df69b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 217 additions and 3 deletions

View File

@ -1,4 +1,5 @@
use crate::err::{self, PyDowncastError, PyErr, PyResult}; use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell}; use crate::pycell::{PyBorrowError, PyBorrowMutError, PyCell};
use crate::pyclass::boolean_struct::{False, True}; use crate::pyclass::boolean_struct::{False, True};
use crate::type_object::HasPyGilRef; use crate::type_object::HasPyGilRef;
@ -66,6 +67,43 @@ pub unsafe trait PyNativeType: Sized {
#[repr(transparent)] #[repr(transparent)]
pub struct Bound<'py, T>(Python<'py>, ManuallyDrop<Py<T>>); pub struct Bound<'py, T>(Python<'py>, ManuallyDrop<Py<T>>);
impl<'py, T> Bound<'py, T>
where
T: PyClass,
{
/// Creates a new instance `Bound<T>` of a `#[pyclass]` on the Python heap.
///
/// # Examples
///
/// ```rust
/// use pyo3::prelude::*;
///
/// #[pyclass]
/// struct Foo {/* fields omitted */}
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<Py<Foo>> {
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo {})?;
/// Ok(foo.into())
/// })?;
/// # Ok(())
/// # }
/// ```
pub fn new(
py: Python<'py>,
value: impl Into<PyClassInitializer<T>>,
) -> PyResult<Bound<'py, T>> {
let initializer = value.into();
let obj = initializer.create_cell(py)?;
let ob = unsafe {
obj.cast::<ffi::PyObject>()
.assume_owned(py)
.downcast_into_unchecked()
};
Ok(ob)
}
}
impl<'py> Bound<'py, PyAny> { impl<'py> Bound<'py, PyAny> {
/// Constructs a new Bound from a pointer. Panics if ptr is null. /// Constructs a new Bound from a pointer. Panics if ptr is null.
/// ///
@ -101,6 +139,149 @@ impl<'py> Bound<'py, PyAny> {
} }
} }
impl<'py, T> Bound<'py, T>
where
T: PyClass,
{
/// Immutably borrows the value `T`.
///
/// This borrow lasts while the returned [`PyRef`] exists.
/// Multiple immutable borrows can be taken out at the same time.
///
/// For frozen classes, the simpler [`get`][Self::get] is available.
///
/// # Examples
///
/// ```rust
/// # use pyo3::prelude::*;
/// #
/// #[pyclass]
/// struct Foo {
/// inner: u8,
/// }
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?;
/// let inner: &u8 = &foo.borrow().inner;
///
/// assert_eq!(*inner, 73);
/// Ok(())
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// # Panics
///
/// Panics if the value is currently mutably borrowed. For a non-panicking variant, use
/// [`try_borrow`](#method.try_borrow).
pub fn borrow(&'py self) -> PyRef<'py, T> {
self.get_cell().borrow()
}
/// Mutably borrows the value `T`.
///
/// This borrow lasts while the returned [`PyRefMut`] exists.
///
/// # Examples
///
/// ```
/// # use pyo3::prelude::*;
/// #
/// #[pyclass]
/// struct Foo {
/// inner: u8,
/// }
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let foo: Bound<'_, Foo> = Bound::new(py, Foo { inner: 73 })?;
/// foo.borrow_mut().inner = 35;
///
/// assert_eq!(foo.borrow().inner, 35);
/// Ok(())
/// })?;
/// # Ok(())
/// # }
/// ```
///
/// # Panics
/// Panics if the value is currently borrowed. For a non-panicking variant, use
/// [`try_borrow_mut`](#method.try_borrow_mut).
pub fn borrow_mut(&'py self) -> PyRefMut<'py, T>
where
T: PyClass<Frozen = False>,
{
self.get_cell().borrow_mut()
}
/// Attempts to immutably borrow the value `T`, returning an error if the value is currently mutably borrowed.
///
/// The borrow lasts while the returned [`PyRef`] exists.
///
/// This is the non-panicking variant of [`borrow`](#method.borrow).
///
/// For frozen classes, the simpler [`get`][Self::get] is available.
pub fn try_borrow(&'py self) -> Result<PyRef<'py, T>, PyBorrowError> {
self.get_cell().try_borrow()
}
/// Attempts to mutably borrow the value `T`, returning an error if the value is currently borrowed.
///
/// The borrow lasts while the returned [`PyRefMut`] exists.
///
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
pub fn try_borrow_mut(&'py self) -> Result<PyRefMut<'py, T>, PyBorrowMutError>
where
T: PyClass<Frozen = False>,
{
self.get_cell().try_borrow_mut()
}
/// Provide an immutable borrow of the value `T` without acquiring the GIL.
///
/// This is available if the class is [`frozen`][macro@crate::pyclass] and [`Sync`].
///
/// # Examples
///
/// ```
/// use std::sync::atomic::{AtomicUsize, Ordering};
/// # use pyo3::prelude::*;
///
/// #[pyclass(frozen)]
/// struct FrozenCounter {
/// value: AtomicUsize,
/// }
///
/// Python::with_gil(|py| {
/// let counter = FrozenCounter { value: AtomicUsize::new(0) };
///
/// let py_counter = Bound::new(py, counter).unwrap();
///
/// py_counter.get().value.fetch_add(1, Ordering::Relaxed);
/// });
/// ```
pub fn get(&self) -> &T
where
T: PyClass<Frozen = True> + Sync,
{
let cell = self.get_cell();
// SAFETY: The class itself is frozen and `Sync` and we do not access anything but `cell.contents.value`.
unsafe { &*cell.get_ptr() }
}
fn get_cell(&'py self) -> &'py PyCell<T> {
let cell = self.as_ptr().cast::<PyCell<T>>();
// SAFETY: Bound<T> is known to contain an object which is laid out in memory as a
// PyCell<T>.
//
// Strictly speaking for now `&'py PyCell<T>` is part of the "GIL Ref" API, so this
// could use some further refactoring later to avoid going through this reference.
unsafe { &*cell }
}
}
impl<'py, T> std::fmt::Debug for Bound<'py, T> { impl<'py, T> std::fmt::Debug for Bound<'py, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let any = self.as_any(); let any = self.as_any();
@ -1768,12 +1949,11 @@ a = A()
use super::*; use super::*;
#[crate::pyclass] #[crate::pyclass(crate = "crate")]
#[pyo3(crate = "crate")]
struct SomeClass(i32); struct SomeClass(i32);
#[test] #[test]
fn instance_borrow_methods() { fn py_borrow_methods() {
// More detailed tests of the underlying semantics in pycell.rs // More detailed tests of the underlying semantics in pycell.rs
Python::with_gil(|py| { Python::with_gil(|py| {
let instance = Py::new(py, SomeClass(0)).unwrap(); let instance = Py::new(py, SomeClass(0)).unwrap();
@ -1791,6 +1971,40 @@ a = A()
}) })
} }
#[test]
fn bound_borrow_methods() {
// More detailed tests of the underlying semantics in pycell.rs
Python::with_gil(|py| {
let instance = Bound::new(py, SomeClass(0)).unwrap();
assert_eq!(instance.borrow().0, 0);
assert_eq!(instance.try_borrow().unwrap().0, 0);
assert_eq!(instance.borrow_mut().0, 0);
assert_eq!(instance.try_borrow_mut().unwrap().0, 0);
instance.borrow_mut().0 = 123;
assert_eq!(instance.borrow().0, 123);
assert_eq!(instance.try_borrow().unwrap().0, 123);
assert_eq!(instance.borrow_mut().0, 123);
assert_eq!(instance.try_borrow_mut().unwrap().0, 123);
})
}
#[crate::pyclass(frozen, crate = "crate")]
struct FrozenClass(i32);
#[test]
fn test_frozen_get() {
Python::with_gil(|py| {
for i in 0..10 {
let instance = Py::new(py, FrozenClass(i)).unwrap();
assert_eq!(instance.get().0, i);
assert_eq!(instance.bind(py).get().0, i);
}
})
}
#[test] #[test]
#[allow(deprecated)] #[allow(deprecated)]
fn cell_tryfrom() { fn cell_tryfrom() {