Skip to content

Commit 43a7cdd

Browse files
committed
deprecate super class initialization from tuples
1 parent c969dd0 commit 43a7cdd

18 files changed

Lines changed: 163 additions & 58 deletions

guide/src/class.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,9 @@ struct SubClass {
447447
#[pymethods]
448448
impl SubClass {
449449
#[new]
450-
fn new() -> (Self, BaseClass) {
451-
(SubClass { val2: 15 }, BaseClass::new())
450+
fn new() -> PyClassInitializer<Self> {
451+
PyClassInitializer::from(BaseClass::new())
452+
.add_subclass(SubClass { val2: 15 })
452453
}
453454

454455
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.

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>> {

tests/test_class_basics.rs

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,8 @@ struct UnsendableChild {}
282282
#[pymethods]
283283
impl UnsendableChild {
284284
#[new]
285-
fn new(value: usize) -> (UnsendableChild, UnsendableBase) {
286-
(UnsendableChild {}, UnsendableBase::new(value))
285+
fn new(value: usize) -> PyClassInitializer<Self> {
286+
PyClassInitializer::from(UnsendableBase::new(value)).add_subclass(UnsendableChild {})
287287
}
288288
}
289289

@@ -489,16 +489,11 @@ struct InheritDict {
489489
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
490490
fn inherited_dict() {
491491
Python::attach(|py| {
492-
let inst = Py::new(
493-
py,
494-
(
495-
InheritDict { _value: 0 },
496-
DunderDictSupport {
497-
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
498-
},
499-
),
500-
)
501-
.unwrap();
492+
let initializer = PyClassInitializer::from(DunderDictSupport {
493+
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
494+
})
495+
.add_subclass(InheritDict { _value: 0 });
496+
let inst = Py::new(py, initializer).unwrap();
502497
py_run!(
503498
py,
504499
inst,
@@ -572,16 +567,11 @@ struct InheritWeakRef {
572567
#[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
573568
fn inherited_weakref() {
574569
Python::attach(|py| {
575-
let inst = Py::new(
576-
py,
577-
(
578-
InheritWeakRef { _value: 0 },
579-
WeakRefSupport {
580-
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
581-
},
582-
),
583-
)
584-
.unwrap();
570+
let initializer = PyClassInitializer::from(WeakRefSupport {
571+
_pad: *b"DEADBEEFDEADBEEFDEADBEEFDEADBEEF",
572+
})
573+
.add_subclass(InheritWeakRef { _value: 0 });
574+
let inst = Py::new(py, initializer).unwrap();
585575
py_run!(
586576
py,
587577
inst,

0 commit comments

Comments
 (0)