Skip to content
Open
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
11 changes: 5 additions & 6 deletions guide/src/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,17 +403,15 @@ Consult the table below to determine which type your constructor should return:
| | **Cannot fail** | **May fail** |
|-----------------------------|---------------------------|-----------------------------------|
|**No inheritance** | `T` | `PyResult<T>` |
|**Inheritance(T Inherits U)**| `(T, U)` | `PyResult<(T, U)>` |
|**Inheritance(General Case)**| [`PyClassInitializer<T>`] | `PyResult<PyClassInitializer<T>>` |
|**Inheritance** | [`PyClassInitializer<T>`] | `PyResult<PyClassInitializer<T>>` |

## Inheritance

By default, `object`, i.e. `PyAny` is used as the base class.
To override this default, use the `extends` parameter for `pyclass` with the full path to the base class.
Currently, only classes defined in Rust and builtins provided by PyO3 can be inherited from; inheriting from other classes defined in Python is not yet supported ([#991](https://github.com/PyO3/pyo3/issues/991)).

For convenience, `(T, U)` implements `Into<PyClassInitializer<T>>` where `U` is the base class of `T`.
But for a more deeply nested inheritance, you have to return `PyClassInitializer<T>` explicitly.
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).
Expand Down Expand Up @@ -447,8 +445,9 @@ struct SubClass {
#[pymethods]
impl SubClass {
#[new]
fn new() -> (Self, BaseClass) {
(SubClass { val2: 15 }, BaseClass::new())
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(BaseClass::new())
.add_subclass(SubClass { val2: 15 })
}

fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {
Expand Down
46 changes: 46 additions & 0 deletions guide/src/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,52 @@ let obj_2 = existing_bound.clone();
# })
```

### Deprecation of super class initialization from tuples

Performing superclass initialitation from a subclass via a tuple is deprecated and will be removed in a future PyO3 version.
This also includes the `From<(S, B)> for PyClassInitializer<S>` impl which we can't warn against.
To migrate use `PyClassInitializer` directly:

Before:

```rust
# #![allow(deprecated)]
# use pyo3::prelude::*;
#[pyclass(subclass)]
struct Base;

#[pyclass(extends=Base)]
struct Sub;

#[pymethods]
impl Sub {
#[new]
fn new() -> (Self, Base) {
(Self, Base)
}
}
```

After:

```rust
# use pyo3::prelude::*;
#[pyclass(subclass)]
struct Base;

#[pyclass(extends=Base)]
struct Sub;

#[pymethods]
impl Sub {
#[new]
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(Base)
.add_subclass(Self)
}
}
```

### Internal change to use multi-phase initialization

[PEP 489](https://peps.python.org/pep-0489/) introduced "multi-phase initialization" for extension modules which provides ways to allocate and clean up per-module state.
Expand Down
1 change: 1 addition & 0 deletions newsfragments/5741.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Super class initialization from tuples is deprecated.
3 changes: 2 additions & 1 deletion pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,7 @@ fn generate_method_body(
} else {
&parse_quote!(())
};
let is_initializer_tuple = quote_spanned! { *output_span => #pyo3_path::impl_::pyclass::IsInitializerTuple::<#output>::VALUE };
let body = quote! {
#text_signature_impl

Expand All @@ -1475,7 +1476,7 @@ fn generate_method_body(
#pyo3_path::impl_::pymethods::tp_new_impl::<
_,
{ #pyo3_path::impl_::pyclass::IsPyClass::<#output>::VALUE },
{ #pyo3_path::impl_::pyclass::IsInitializerTuple::<#output>::VALUE }
{ #is_initializer_tuple }
>(py, result, _slf)
};
(arg_idents, arg_types, body)
Expand Down
4 changes: 2 additions & 2 deletions pytests/src/subclassing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ pub mod subclassing {
#[pymethods]
impl Subclass {
#[new]
fn new() -> (Self, Subclassable) {
(Subclass {}, Subclassable::new())
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(Subclassable::new()).add_subclass(Self {})
}

fn __str__(&self) -> &'static str {
Expand Down
8 changes: 8 additions & 0 deletions src/impl_/pyclass/probes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ where
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
#[deprecated(
since = "0.28.0",
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
)]
pub const VALUE: bool = true;
}
impl<S, B, E> IsInitializerTuple<Result<(S, B), E>>
Expand All @@ -122,6 +126,10 @@ where
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
#[deprecated(
since = "0.28.0",
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
)]
pub const VALUE: bool = true;
}

Expand Down
13 changes: 9 additions & 4 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,8 @@ where
/// struct SubClass;
///
/// Python::attach(|py| {
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
/// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
/// let obj = Bound::new(py, initializer).unwrap();
/// assert!(obj.as_super().pyrepr().is_ok());
/// })
/// # }
Expand Down Expand Up @@ -738,7 +739,8 @@ where
/// struct SubClass;
///
/// Python::attach(|py| {
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
/// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
/// let obj = Bound::new(py, initializer).unwrap();
/// assert!(obj.into_super().pyrepr().is_ok());
/// })
/// # }
Expand Down Expand Up @@ -2865,6 +2867,7 @@ a = A()
#[cfg(feature = "macros")]
mod using_macros {
use super::*;
use crate::PyClassInitializer;

#[crate::pyclass(crate = "crate")]
struct SomeClass(i32);
Expand Down Expand Up @@ -2943,7 +2946,8 @@ a = A()
#[test]
fn test_as_super() {
Python::attach(|py| {
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
let obj = Bound::new(py, initializer).unwrap();
let _: &Bound<'_, BaseClass> = obj.as_super();
let _: &Bound<'_, PyAny> = obj.as_super().as_super();
assert!(obj.as_super().pyrepr_by_ref().is_ok());
Expand All @@ -2953,7 +2957,8 @@ a = A()
#[test]
fn test_into_super() {
Python::attach(|py| {
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
let obj = Bound::new(py, initializer).unwrap();
let _: Bound<'_, BaseClass> = obj.clone().into_super();
let _: Bound<'_, PyAny> = obj.clone().into_super().into_super();
assert!(obj.into_super().pyrepr_by_val().is_ok());
Expand Down
10 changes: 6 additions & 4 deletions src/pycell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ use impl_::{PyClassBorrowChecker, PyClassObjectBaseLayout, PyClassObjectLayout};
/// #[pymethods]
/// impl Child {
/// #[new]
/// fn new() -> (Self, Parent) {
/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" })
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Parent { basename: "Butterfly" })
/// .add_subclass(Child { name: "Caterpillar" })
/// }
///
/// fn format(slf: PyRef<'_, Self>) -> String {
Expand Down Expand Up @@ -430,8 +431,9 @@ where
/// #[pymethods]
/// impl Sub {
/// #[new]
/// fn new() -> (Self, Base) {
/// (Self { sub_name: "sub_name" }, Base { base_name: "base_name" })
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Base { base_name: "base_name" })
/// .add_subclass(Self { sub_name: "sub_name" })
/// }
/// fn sub_name_len(&self) -> usize {
/// self.sub_name.len()
Expand Down
10 changes: 6 additions & 4 deletions src/pyclass/guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ use std::ptr::NonNull;
/// #[pymethods]
/// impl Child {
/// #[new]
/// fn new() -> (Self, Parent) {
/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" })
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Parent { basename: "Butterfly" })
/// .add_subclass(Child { name: "Caterpillar" })
/// }
///
/// fn format(slf: PyClassGuard<'_, Self>) -> String {
Expand Down Expand Up @@ -184,8 +185,9 @@ where
/// #[pymethods]
/// impl Sub {
/// #[new]
/// fn new() -> (Self, Base) {
/// (Self { sub_name: "sub_name" }, Base { base_name: "base_name" })
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(Base { base_name: "base_name" })
/// .add_subclass(Self { sub_name: "sub_name" })
/// }
/// fn sub_name_len(&self) -> usize {
/// self.sub_name.len()
Expand Down
4 changes: 2 additions & 2 deletions src/types/pysuper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ impl PySuper {
/// #[pymethods]
/// impl SubClass {
/// #[new]
/// fn new() -> (Self, BaseClass) {
/// (SubClass {}, BaseClass::new())
/// fn new() -> PyClassInitializer<Self> {
/// PyClassInitializer::from(BaseClass::new()).add_subclass(SubClass {})
/// }
///
/// fn method<'py>(self_: &Bound<'py, Self>) -> PyResult<Bound<'py, PyAny>> {
Expand Down
34 changes: 12 additions & 22 deletions tests/test_class_basics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ struct UnsendableChild {}
#[pymethods]
impl UnsendableChild {
#[new]
fn new(value: usize) -> (UnsendableChild, UnsendableBase) {
(UnsendableChild {}, UnsendableBase::new(value))
fn new(value: usize) -> PyClassInitializer<Self> {
PyClassInitializer::from(UnsendableBase::new(value)).add_subclass(UnsendableChild {})
}
}

Expand Down Expand Up @@ -489,16 +489,11 @@ struct InheritDict {
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn inherited_dict() {
Python::attach(|py| {
let inst = Py::new(
py,
(
InheritDict { _value: 0 },
DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
),
)
.unwrap();
let initializer = PyClassInitializer::from(DunderDictSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
})
.add_subclass(InheritDict { _value: 0 });
let inst = Py::new(py, initializer).unwrap();
py_run!(
py,
inst,
Expand Down Expand Up @@ -572,16 +567,11 @@ struct InheritWeakRef {
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
fn inherited_weakref() {
Python::attach(|py| {
let inst = Py::new(
py,
(
InheritWeakRef { _value: 0 },
WeakRefSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
},
),
)
.unwrap();
let initializer = PyClassInitializer::from(WeakRefSupport {
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
})
.add_subclass(InheritWeakRef { _value: 0 });
let inst = Py::new(py, initializer).unwrap();
py_run!(
py,
inst,
Expand Down
8 changes: 6 additions & 2 deletions tests/test_class_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ fn test_polymorphic_container_does_not_accept_other_types() {
#[test]
fn test_pyref_as_base() {
Python::attach(|py| {
let cell = Bound::new(py, (SubClass {}, BaseClass { value: 120 })).unwrap();
let initializer =
PyClassInitializer::from(BaseClass { value: 120 }).add_subclass(SubClass {});
let cell = Bound::new(py, initializer).unwrap();

// First try PyRefMut
let sub: PyRefMut<'_, SubClass> = cell.borrow_mut();
Expand All @@ -142,7 +144,9 @@ fn test_pyref_as_base() {
#[test]
fn test_pycell_deref() {
Python::attach(|py| {
let obj = Bound::new(py, (SubClass {}, BaseClass { value: 120 })).unwrap();
let initializer =
PyClassInitializer::from(BaseClass { value: 120 }).add_subclass(SubClass {});
let obj = Bound::new(py, initializer).unwrap();

// Should be able to deref as PyAny
assert_eq!(
Expand Down
8 changes: 4 additions & 4 deletions tests/test_class_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ struct SubWithoutInit;
#[pymethods]
impl SubWithoutInit {
#[new]
fn new() -> (Self, Base) {
(Self, Base::new())
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(Base::new()).add_subclass(Self)
}
}

Expand All @@ -60,8 +60,8 @@ struct SubWithInit;
#[pymethods]
impl SubWithInit {
#[new]
fn new() -> (Self, Base) {
(Self, Base::new())
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(Base::new()).add_subclass(Self)
}

fn __init__(mut slf: pyo3::PyClassGuardMut<'_, Self>) {
Expand Down
1 change: 1 addition & 0 deletions tests/test_compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ fn test_compile_errors() {
let t = trybuild::TestCases::new();

t.compile_fail("tests/ui/deprecated_pyfn.rs");
t.compile_fail("tests/ui/deprecated_super_init.rs");
#[cfg(not(feature = "experimental-inspect"))]
t.compile_fail("tests/ui/invalid_property_args.rs");
t.compile_fail("tests/ui/invalid_proto_pymethods.rs");
Expand Down
8 changes: 3 additions & 5 deletions tests/test_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,9 @@ fn inheritance_with_new_methods_with_drop() {
#[pymethods]
impl SubClassWithDrop {
#[new]
fn new() -> (Self, BaseClassWithDrop) {
(
SubClassWithDrop { guard: None },
BaseClassWithDrop { guard: None },
)
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(BaseClassWithDrop { guard: None })
.add_subclass(SubClassWithDrop { guard: None })
}
}

Expand Down
8 changes: 4 additions & 4 deletions tests/test_inheritance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ struct SubClass {
#[pymethods]
impl SubClass {
#[new]
fn new() -> (Self, BaseClass) {
(SubClass { val2: 5 }, BaseClass { val1: 10 })
fn new() -> PyClassInitializer<Self> {
PyClassInitializer::from(BaseClass { val1: 10 }).add_subclass(SubClass { val2: 5 })
}
fn sub_method(&self, x: usize) -> usize {
x * self.val2
Expand Down Expand Up @@ -146,9 +146,9 @@ struct SubClass2 {}
#[pymethods]
impl SubClass2 {
#[new]
fn new(value: isize) -> PyResult<(Self, BaseClassWithResult)> {
fn new(value: isize) -> PyResult<PyClassInitializer<Self>> {
let base = BaseClassWithResult::new(value)?;
Ok((Self {}, base))
Ok(PyClassInitializer::from(base).add_subclass(Self {}))
}
}

Expand Down
Loading
Loading