Update class.md and Change super API

This commit is contained in:
kngwyu 2020-02-18 00:16:11 +09:00
parent 2fd2185052
commit daca04e5f3
3 changed files with 95 additions and 43 deletions

View File

@ -22,6 +22,7 @@ Specifically, the following implementation is generated:
```rust
use pyo3::prelude::*;
use pyo3::types::PyAny;
/// Class for demonstration
struct MyClass {
@ -33,9 +34,10 @@ impl pyo3::pyclass::PyClassAlloc for MyClass {}
unsafe impl pyo3::PyTypeInfo for MyClass {
type Type = MyClass;
type BaseType = pyo3::types::PyAny;
type ConcreteLayout = pyo3::PyCell<Self>;
type Initializer = pyo3::PyClassInitializer<Self>;
type BaseType = PyAny;
type BaseLayout = pyo3::pycell::PyCellBase<PyAny>;
type Layout = PyCell<Self>;
type Initializer = PyClassInitializer<Self>;
const NAME: &'static str = "MyClass";
const MODULE: Option<&'static str> = None;
@ -53,6 +55,7 @@ unsafe impl pyo3::PyTypeInfo for MyClass {
impl pyo3::pyclass::PyClass for MyClass {
type Dict = pyo3::pyclass_slots::PyClassDummySlot;
type WeakRef = pyo3::pyclass_slots::PyClassDummySlot;
type BaseNativeType = PyAny;
}
impl pyo3::IntoPy<PyObject> for MyClass {
@ -105,18 +108,26 @@ fn mymodule(_py: Python, m: &PyModule) -> PyResult<()> {
}
```
## Get Python objects from `pyclass`
You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it).
## PyCell and interior mutability
You sometimes need to convert your `pyclass` into a Python object and access it
from Rust code (e.g., for testing it).
`PyCell` is our primary interface for that.
For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`,
you can use `PyCell<T>`.
Or you can use `Py<T>` directly, for *not-GIL-bounded* references.
`PyCell<T: PyClass>` is always allocated in the Python heap, so we don't have the ownership of it.
We can get `&PyCell<T>`, not `PyCell<T>`.
### `PyCell`
`PyCell` represents the actual layout of `pyclass` on the Python heap.
Thus, to mutate data behind `&PyCell` safely, we employs
[Interior Mutability Pattern](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html)
like [std::cell::RefCell](https://doc.rust-lang.org/std/cell/struct.RefCell.html).
If you want to instantiate `pyclass` in Python and get the reference,
you can use `PyCell::new_ref` or `PyCell::new_mut`.
Users who are familiar with `RefCell` can use `PyCell` just like `RefCell`.
For users who doesn't know `RefCell` well, we repeat the Rust's borrowing rule here:
- 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` ensures these borrowing rules by managing a reference counter.
TODO: link to the API document
```rust
# use pyo3::prelude::*;
@ -124,28 +135,31 @@ you can use `PyCell::new_ref` or `PyCell::new_mut`.
# use pyo3::PyCell;
#[pyclass]
struct MyClass {
#[pyo3(get)]
num: i32,
debug: bool,
}
let gil = Python::acquire_gil();
let py = gil.python();
let obj = PyCell::new_ref(py, MyClass { num: 3, debug: true }).unwrap();
// You can use deref
assert_eq!(obj.num, 3);
let dict = PyDict::new(py);
// You can treat a `&PyCell` as a normal Python object
dict.set_item("obj", obj).unwrap();
// return &mut PyCell<MyClass>
let obj = PyCell::new_mut(py, MyClass { num: 3, debug: true }).unwrap();
obj.num = 5;
let obj = PyCell::new(py, MyClass { num: 3, debug: true }).unwrap();
{
let obj_ref = obj.borrow(); // Get PyRef
assert_eq!(obj_ref.num, 3);
// You cannot get PyRefMut unless all PyRef drop
assert!(obj.try_borrow_mut().is_err());
}
{
let mut obj_mut = obj.borrow_mut(); // Get PyRefMut
obj_mut.num = 5;
// You cannot get PyRef unless all PyRefMut drop
assert!(obj.try_borrow().is_err());
}
// You can convert `&PyCell` to Python object
pyo3::py_run!(py, obj, "assert obj.num == 5")
```
### `Py`
`Py` is an object wrapper which stores an object longer than the GIL lifetime.
You can use it to avoid lifetime problems.
`&PyCell<T>` is bouded by the same lifetime as `GILGuard`.
To avoid this you can use `Py<T>`, which stores an object longer than the GIL lifetime.
```rust
# use pyo3::prelude::*;
#[pyclass]
@ -159,7 +173,9 @@ fn return_myclass() -> Py<MyClass> {
}
let gil = Python::acquire_gil();
let obj = return_myclass();
assert_eq!(obj.as_ref(gil.python()).num, 1);
let cell = obj.as_ref(gil.python()); // AsPyRef::as_ref returns &PyCell
let obj_ref = cell.borrow(); // Get PyRef<T>
assert_eq!(obj_ref.num, 1);
```
## Customizing the class
@ -228,6 +244,9 @@ baseclass of `T`.
But for more deeply nested inheritance, you have to return `PyClassInitializer<T>`
explicitly.
To get a parent class from child, use `PyRef<T>` instead of `&self`,
or `PyRefMut<T>` instead of `&mut self`.
```rust
# use pyo3::prelude::*;
use pyo3::PyCell;
@ -261,8 +280,9 @@ impl SubClass {
(SubClass{ val2: 15}, BaseClass::new())
}
fn method2(self_: &PyCell<Self>) -> PyResult<usize> {
self_.get_super().method().map(|x| x * self_.val2)
fn method2(self_: PyRef<Self>) -> PyResult<usize> {
let super_ = self_.as_super(); // Get &BaseClass
super_.method().map(|x| x * self_.val2)
}
}
@ -279,16 +299,17 @@ impl SubSubClass {
.add_subclass(SubSubClass{val3: 20})
}
fn method3(self_: &PyCell<Self>) -> PyResult<usize> {
let super_ = self_.get_super();
SubClass::method2(super_).map(|x| x * self_.val3)
fn method3(self_: PyRef<Self>) -> PyResult<usize> {
let v = self_.val3;
let super_ = self_.into_super(); // Get PyRef<SubClass>
SubClass::method2(super_).map(|x| x * v)
}
}
# let gil = Python::acquire_gil();
# let py = gil.python();
# let subsub = pyo3::PyCell::new_ref(py, SubSubClass::new()).unwrap();
# let subsub = pyo3::PyCell::new(py, SubSubClass::new()).unwrap();
# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000")
```
@ -761,8 +782,8 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol
Iterators can be defined using the
[`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait.
It includes two methods `__iter__` and `__next__`:
* `fn __iter__(slf: &mut PyCell<Self>) -> PyResult<impl IntoPy<PyObject>>`
* `fn __next__(slf: &mut PyCell<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
* `fn __iter__(slf: PyRefMut<Self>) -> PyResult<impl IntoPy<PyObject>>`
* `fn __next__(slf: PyRefMut<Self>) -> PyResult<Option<impl IntoPy<PyObject>>>`
Returning `Ok(None)` from `__next__` indicates that that there are no further items.
@ -779,10 +800,10 @@ struct MyIterator {
#[pyproto]
impl PyIterProtocol for MyIterator {
fn __iter__(slf: &mut PyCell<Self>) -> PyResult<Py<MyIterator>> {
fn __iter__(mut slf: PyRefMut<Self>) -> PyResult<Py<MyIterator>> {
Ok(slf.into())
}
fn __next__(slf: &mut PyCell<Self>) -> PyResult<Option<PyObject>> {
fn __next__(mut slf: PyRefMut<Self>) -> PyResult<Option<PyObject>> {
Ok(slf.iter.next())
}
}

View File

@ -61,7 +61,7 @@ let userdata = UserData {
id: 34,
name: "Yu".to_string(),
};
let userdata = PyCell::new_ref(py, userdata).unwrap();
let userdata = PyCell::new(py, userdata).unwrap();
let userdata_as_tuple = (34, "Yu");
py_run!(py, userdata userdata_as_tuple, r#"
assert repr(userdata) == "User Yu(id: 34)"

View File

@ -258,12 +258,29 @@ pub struct PyRef<'p, T: PyClass> {
inner: &'p PyCellInner<T>,
}
impl<'p, T: PyClass> PyRef<'p, T> {
pub fn get_super(&'p self) -> &'p T::BaseType {
impl<'p, T> PyRef<'p, T>
where
T: PyClass,
{
pub fn as_super(&'p self) -> &'p T::BaseType {
unsafe { self.inner.ob_base.unchecked_ref() }
}
}
impl<'p, T, U> PyRef<'p, T>
where
T: PyClass + PyTypeInfo<BaseType = U, BaseLayout = PyCellInner<U>>,
U: PyClass,
{
pub fn into_super(self) -> PyRef<'p, U> {
let res = PyRef {
inner: &self.inner.ob_base,
};
std::mem::forget(self); // Avoid drop
res
}
}
impl<'p, T: PyClass> Deref for PyRef<'p, T> {
type Target = T;
@ -310,14 +327,28 @@ pub struct PyRefMut<'p, T: PyClass> {
}
impl<'p, T: PyClass> PyRefMut<'p, T> {
pub fn get_super(&'p self) -> &'p T::BaseType {
pub fn as_super(&'p self) -> &'p T::BaseType {
unsafe { self.inner.ob_base.unchecked_ref() }
}
pub fn get_super_mut(&'p self) -> &'p mut T::BaseType {
pub fn as_super_mut(&'p mut self) -> &'p mut T::BaseType {
unsafe { self.inner.ob_base.unchecked_refmut() }
}
}
impl<'p, T, U> PyRefMut<'p, T>
where
T: PyClass + PyTypeInfo<BaseType = U, BaseLayout = PyCellInner<U>>,
U: PyClass,
{
pub fn into_super(self) -> PyRefMut<'p, U> {
let res = PyRefMut {
inner: &self.inner.ob_base,
};
std::mem::forget(self); // Avoid drop
res
}
}
impl<'p, T: PyClass> Deref for PyRefMut<'p, T> {
type Target = T;