Skip to content

Commit 820716e

Browse files
committed
deprecate super class initialization from tuples
1 parent eef6d64 commit 820716e

19 files changed

Lines changed: 166 additions & 62 deletions

guide/src/class.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -403,17 +403,15 @@ Consult the table below to determine which type your constructor should return:
403403
| | **Cannot fail** | **May fail** |
404404
|-----------------------------|---------------------------|-----------------------------------|
405405
|**No inheritance** | `T` | `PyResult<T>` |
406-
|**Inheritance(T Inherits U)**| `(T, U)` | `PyResult<(T, U)>` |
407-
|**Inheritance(General Case)**| [`PyClassInitializer<T>`] | `PyResult<PyClassInitializer<T>>` |
406+
|**Inheritance** | [`PyClassInitializer<T>`] | `PyResult<PyClassInitializer<T>>` |
408407

409408
## Inheritance
410409

411410
By default, `object`, i.e. `PyAny` is used as the base class.
412411
To override this default, use the `extends` parameter for `pyclass` with the full path to the base class.
413412
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)).
414413

415-
For convenience, `(T, U)` implements `Into<PyClassInitializer<T>>` where `U` is the base class of `T`.
416-
But for a more deeply nested inheritance, you have to return `PyClassInitializer<T>` explicitly.
414+
To initialize a class, which inherits from another class, use the `PyClassInitializer` API.
417415

418416
To get a parent class from a child, use [`PyRef`] instead of `&self` for methods, or [`PyRefMut`] instead of `&mut self`.
419417
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).
@@ -447,8 +445,9 @@ struct SubClass {
447445
#[pymethods]
448446
impl SubClass {
449447
#[new]
450-
fn new() -> (Self, BaseClass) {
451-
(SubClass { val2: 15 }, BaseClass::new())
448+
fn new() -> PyClassInitializer<Self> {
449+
PyClassInitializer::from(BaseClass::new())
450+
.add_subclass(SubClass { val2: 15 })
452451
}
453452

454453
fn method2(self_: PyRef<'_, Self>) -> PyResult<usize> {

guide/src/migration.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,52 @@ let obj_2 = existing_bound.clone();
8888
# })
8989
```
9090

91+
### Deprecation of super class initialization from tuples
92+
93+
Performing superclass initialitation from a subclass via a tuple is deprecated and will be removed in a future PyO3 version.
94+
This also includes the `From<(S, B)> for PyClassInitializer<S>` impl which we can't warn against.
95+
To migrate use `PyClassInitializer` directly:
96+
97+
Before:
98+
99+
```rust
100+
# #![allow(deprecated)]
101+
# use pyo3::prelude::*;
102+
#[pyclass(subclass)]
103+
struct Base;
104+
105+
#[pyclass(extends=Base)]
106+
struct Sub;
107+
108+
#[pymethods]
109+
impl Sub {
110+
#[new]
111+
fn new() -> (Self, Base) {
112+
(Self, Base)
113+
}
114+
}
115+
```
116+
117+
After:
118+
119+
```rust
120+
# use pyo3::prelude::*;
121+
#[pyclass(subclass)]
122+
struct Base;
123+
124+
#[pyclass(extends=Base)]
125+
struct Sub;
126+
127+
#[pymethods]
128+
impl Sub {
129+
#[new]
130+
fn new() -> PyClassInitializer<Self> {
131+
PyClassInitializer::from(Base)
132+
.add_subclass(Self)
133+
}
134+
}
135+
```
136+
91137
### Internal change to use multi-phase initialization
92138

93139
[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.

newsfragments/5741.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Super class initialization from tuples is deprecated.

pyo3-macros-backend/src/pymethod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1465,6 +1465,7 @@ fn generate_method_body(
14651465
} else {
14661466
&parse_quote!(())
14671467
};
1468+
let is_initializer_tuple = quote_spanned! { *output_span => #pyo3_path::impl_::pyclass::IsInitializerTuple::<#output>::VALUE };
14681469
let body = quote! {
14691470
#text_signature_impl
14701471

@@ -1475,7 +1476,7 @@ fn generate_method_body(
14751476
#pyo3_path::impl_::pymethods::tp_new_impl::<
14761477
_,
14771478
{ #pyo3_path::impl_::pyclass::IsPyClass::<#output>::VALUE },
1478-
{ #pyo3_path::impl_::pyclass::IsInitializerTuple::<#output>::VALUE }
1479+
{ #is_initializer_tuple }
14791480
>(py, result, _slf)
14801481
};
14811482
(arg_idents, arg_types, body)

pytests/src/subclassing.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ pub mod subclassing {
2929
#[pymethods]
3030
impl Subclass {
3131
#[new]
32-
fn new() -> (Self, Subclassable) {
33-
(Subclass {}, Subclassable::new())
32+
fn new() -> PyClassInitializer<Self> {
33+
PyClassInitializer::from(Subclassable::new()).add_subclass(Self {})
3434
}
3535

3636
fn __str__(&self) -> &'static str {

src/impl_/pyclass/probes.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ where
114114
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
115115
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
116116
{
117+
#[deprecated(
118+
since = "0.28.0",
119+
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
120+
)]
117121
pub const VALUE: bool = true;
118122
}
119123
impl<S, B, E> IsInitializerTuple<Result<(S, B), E>>
@@ -122,6 +126,10 @@ where
122126
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
123127
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
124128
{
129+
#[deprecated(
130+
since = "0.28.0",
131+
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
132+
)]
125133
pub const VALUE: bool = true;
126134
}
127135

src/instance.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,8 @@ where
686686
/// struct SubClass;
687687
///
688688
/// Python::attach(|py| {
689-
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
689+
/// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
690+
/// let obj = Bound::new(py, initializer).unwrap();
690691
/// assert!(obj.as_super().pyrepr().is_ok());
691692
/// })
692693
/// # }
@@ -738,7 +739,8 @@ where
738739
/// struct SubClass;
739740
///
740741
/// Python::attach(|py| {
741-
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
742+
/// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
743+
/// let obj = Bound::new(py, initializer).unwrap();
742744
/// assert!(obj.into_super().pyrepr().is_ok());
743745
/// })
744746
/// # }
@@ -2865,6 +2867,7 @@ a = A()
28652867
#[cfg(feature = "macros")]
28662868
mod using_macros {
28672869
use super::*;
2870+
use crate::PyClassInitializer;
28682871

28692872
#[crate::pyclass(crate = "crate")]
28702873
struct SomeClass(i32);
@@ -2943,7 +2946,8 @@ a = A()
29432946
#[test]
29442947
fn test_as_super() {
29452948
Python::attach(|py| {
2946-
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
2949+
let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
2950+
let obj = Bound::new(py, initializer).unwrap();
29472951
let _: &Bound<'_, BaseClass> = obj.as_super();
29482952
let _: &Bound<'_, PyAny> = obj.as_super().as_super();
29492953
assert!(obj.as_super().pyrepr_by_ref().is_ok());
@@ -2953,7 +2957,8 @@ a = A()
29532957
#[test]
29542958
fn test_into_super() {
29552959
Python::attach(|py| {
2956-
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
2960+
let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
2961+
let obj = Bound::new(py, initializer).unwrap();
29572962
let _: Bound<'_, BaseClass> = obj.clone().into_super();
29582963
let _: Bound<'_, PyAny> = obj.clone().into_super().into_super();
29592964
assert!(obj.into_super().pyrepr_by_val().is_ok());

src/pycell.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,9 @@ use impl_::{PyClassBorrowChecker, PyClassObjectBaseLayout, PyClassObjectLayout};
233233
/// #[pymethods]
234234
/// impl Child {
235235
/// #[new]
236-
/// fn new() -> (Self, Parent) {
237-
/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" })
236+
/// fn new() -> PyClassInitializer<Self> {
237+
/// PyClassInitializer::from(Parent { basename: "Butterfly" })
238+
/// .add_subclass(Child { name: "Caterpillar" })
238239
/// }
239240
///
240241
/// fn format(slf: PyRef<'_, Self>) -> String {
@@ -430,8 +431,9 @@ where
430431
/// #[pymethods]
431432
/// impl Sub {
432433
/// #[new]
433-
/// fn new() -> (Self, Base) {
434-
/// (Self { sub_name: "sub_name" }, Base { base_name: "base_name" })
434+
/// fn new() -> PyClassInitializer<Self> {
435+
/// PyClassInitializer::from(Base { base_name: "base_name" })
436+
/// .add_subclass(Self { sub_name: "sub_name" })
435437
/// }
436438
/// fn sub_name_len(&self) -> usize {
437439
/// self.sub_name.len()

src/pyclass/guard.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ use std::ptr::NonNull;
6262
/// #[pymethods]
6363
/// impl Child {
6464
/// #[new]
65-
/// fn new() -> (Self, Parent) {
66-
/// (Child { name: "Caterpillar" }, Parent { basename: "Butterfly" })
65+
/// fn new() -> PyClassInitializer<Self> {
66+
/// PyClassInitializer::from(Parent { basename: "Butterfly" })
67+
/// .add_subclass(Child { name: "Caterpillar" })
6768
/// }
6869
///
6970
/// fn format(slf: PyClassGuard<'_, Self>) -> String {
@@ -184,8 +185,9 @@ where
184185
/// #[pymethods]
185186
/// impl Sub {
186187
/// #[new]
187-
/// fn new() -> (Self, Base) {
188-
/// (Self { sub_name: "sub_name" }, Base { base_name: "base_name" })
188+
/// fn new() -> PyClassInitializer<Self> {
189+
/// PyClassInitializer::from(Base { base_name: "base_name" })
190+
/// .add_subclass(Self { sub_name: "sub_name" })
189191
/// }
190192
/// fn sub_name_len(&self) -> usize {
191193
/// self.sub_name.len()

src/types/pysuper.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ impl PySuper {
6464
/// #[pymethods]
6565
/// impl SubClass {
6666
/// #[new]
67-
/// fn new() -> (Self, BaseClass) {
68-
/// (SubClass {}, BaseClass::new())
67+
/// fn new() -> PyClassInitializer<Self> {
68+
/// PyClassInitializer::from(BaseClass::new()).add_subclass(SubClass {})
6969
/// }
7070
///
7171
/// fn method<'py>(self_: &Bound<'py, Self>) -> PyResult<Bound<'py, PyAny>> {

0 commit comments

Comments
 (0)