Skip to content

Commit 3d526e2

Browse files
committed
deprecate super class initialization from tuples
1 parent 6917b35 commit 3d526e2

20 files changed

Lines changed: 192 additions & 114 deletions

guide/src/class.md

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

411410
## Inheritance
412411

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

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

420418
To get a parent class from a child, use [`PyRef`] instead of `&self` for methods, or [`PyRefMut`] instead of `&mut self`.
421419
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).
@@ -449,8 +447,9 @@ struct SubClass {
449447
#[pymethods]
450448
impl SubClass {
451449
#[new]
452-
fn new() -> (Self, BaseClass) {
453-
(SubClass { val2: 15 }, BaseClass::new())
450+
fn new() -> PyClassInitializer<Self> {
451+
PyClassInitializer::from(BaseClass::new())
452+
.add_subclass(SubClass { val2: 15 })
454453
}
455454

456455
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
@@ -47,6 +47,52 @@ In PyO3 0.29, `pyo3-build-config` no longer inlines any configuration into itsel
4747
This has upside of faster compilation - `pyo3-build-config` no longer has a build script and never recompiles when changing Python version.
4848
It also guarantees that all builds are based on the single fully-configured build configuration resolved by `pyo3-ffi`.
4949

50+
### Deprecation of super class initialization from tuples
51+
52+
Performing superclass initialitation from a subclass via a tuple is deprecated and will be removed in a future PyO3 version.
53+
This also includes the `From<(S, B)> for PyClassInitializer<S>` impl which we can't warn against.
54+
To migrate use `PyClassInitializer` directly:
55+
56+
Before:
57+
58+
```rust
59+
# #![allow(deprecated)]
60+
# use pyo3::prelude::*;
61+
#[pyclass(subclass)]
62+
struct Base;
63+
64+
#[pyclass(extends=Base)]
65+
struct Sub;
66+
67+
#[pymethods]
68+
impl Sub {
69+
#[new]
70+
fn new() -> (Self, Base) {
71+
(Self, Base)
72+
}
73+
}
74+
```
75+
76+
After:
77+
78+
```rust
79+
# use pyo3::prelude::*;
80+
#[pyclass(subclass)]
81+
struct Base;
82+
83+
#[pyclass(extends=Base)]
84+
struct Sub;
85+
86+
#[pymethods]
87+
impl Sub {
88+
#[new]
89+
fn new() -> PyClassInitializer<Self> {
90+
PyClassInitializer::from(Base)
91+
.add_subclass(Self)
92+
}
93+
}
94+
```
95+
5096
## from 0.27.* to 0.28
5197

5298
### Default to supporting free-threaded Python

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: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,14 +1483,19 @@ fn generate_method_body(
14831483
#pyo3_path::impl_::pymethods::tp_new_impl::<_, #cls>(#py, #initializer, #slf)
14841484
};
14851485

1486+
let value = syn::Ident::new("value", Span::call_site());
1487+
let resolver = quote_spanned! { *output_span =>
1488+
#pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&#value).resolve(#value);
1489+
};
1490+
14861491
let body = quote! {
14871492
#text_signature_impl
14881493
#warnings
14891494
#arg_convert
14901495

14911496
let result = #call;
1492-
let value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?;
1493-
let #initializer = #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&value).resolve(value);
1497+
let #value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?;
1498+
let #initializer = #resolver;
14941499
unsafe { #conversion }
14951500
};
14961501
(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: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use core::marker::PhantomData;
22

33
use crate::conversion::IntoPyObject;
4-
use crate::impl_::pyclass::PyClassBaseType;
5-
use crate::impl_::pyclass_init::PyNativeTypeInitializer;
6-
use crate::{FromPyObject, Py, PyClass, PyClassInitializer};
4+
use crate::{FromPyObject, Py};
75

86
/// Trait used to combine with zero-sized types to calculate at compile time
97
/// some property of a type.
@@ -98,41 +96,6 @@ impl<E> IsReturningEmptyTuple<Result<(), E>> {
9896
pub const VALUE: bool = true;
9997
}
10098

101-
probe!(IsPyClass);
102-
103-
impl<T> IsPyClass<T>
104-
where
105-
T: PyClass,
106-
{
107-
pub const VALUE: bool = true;
108-
}
109-
110-
impl<T, E> IsPyClass<Result<T, E>>
111-
where
112-
T: PyClass,
113-
{
114-
pub const VALUE: bool = true;
115-
}
116-
117-
probe!(IsInitializerTuple);
118-
119-
impl<S, B> IsInitializerTuple<(S, B)>
120-
where
121-
S: PyClass<BaseType = B>,
122-
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
123-
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
124-
{
125-
pub const VALUE: bool = true;
126-
}
127-
impl<S, B, E> IsInitializerTuple<Result<(S, B), E>>
128-
where
129-
S: PyClass<BaseType = B>,
130-
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
131-
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
132-
{
133-
pub const VALUE: bool = true;
134-
}
135-
13699
#[cfg(test)]
137100
macro_rules! value_of {
138101
($probe:ident, $ty:ty) => {{

src/instance.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,8 @@ where
685685
/// struct SubClass;
686686
///
687687
/// Python::attach(|py| {
688-
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
688+
/// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
689+
/// let obj = Bound::new(py, initializer).unwrap();
689690
/// assert!(obj.as_super().pyrepr().is_ok());
690691
/// })
691692
/// # }
@@ -737,7 +738,8 @@ where
737738
/// struct SubClass;
738739
///
739740
/// Python::attach(|py| {
740-
/// let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
741+
/// let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
742+
/// let obj = Bound::new(py, initializer).unwrap();
741743
/// assert!(obj.into_super().pyrepr().is_ok());
742744
/// })
743745
/// # }
@@ -2929,6 +2931,7 @@ a = A()
29292931
#[cfg(feature = "macros")]
29302932
mod using_macros {
29312933
use super::*;
2934+
use crate::PyClassInitializer;
29322935

29332936
#[crate::pyclass(crate = "crate")]
29342937
struct SomeClass(i32);
@@ -3009,7 +3012,8 @@ a = A()
30093012
#[test]
30103013
fn test_as_super() {
30113014
Python::attach(|py| {
3012-
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
3015+
let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
3016+
let obj = Bound::new(py, initializer).unwrap();
30133017
let _: &Bound<'_, BaseClass> = obj.as_super();
30143018
let _: &Bound<'_, PyAny> = obj.as_super().as_super();
30153019
assert!(obj.as_super().pyrepr_by_ref().is_ok());
@@ -3019,7 +3023,8 @@ a = A()
30193023
#[test]
30203024
fn test_into_super() {
30213025
Python::attach(|py| {
3022-
let obj = Bound::new(py, (SubClass, BaseClass)).unwrap();
3026+
let initializer = PyClassInitializer::from(BaseClass).add_subclass(SubClass);
3027+
let obj = Bound::new(py, initializer).unwrap();
30233028
let _: Bound<'_, BaseClass> = obj.clone().into_super();
30243029
let _: Bound<'_, PyAny> = obj.clone().into_super().into_super();
30253030
assert!(obj.into_super().pyrepr_by_val().is_ok());

src/internal/pyclass_init.rs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
6060
}
6161
}
6262

63-
pub struct TpNewValueTypeResolver<ClassT, ValueT>(
63+
pub struct TpNewValueTypeResolver<ClassT, ValueT>(TpNewTupleResolver<ClassT, ValueT>);
64+
pub struct TpNewTupleResolver<ClassT, ValueT>(
6465
ResolveToArbitraryObject,
6566
PhantomData<(ClassT, ValueT)>,
6667
);
@@ -76,10 +77,20 @@ pub struct ResolveToArbitraryObject(());
7677
/// `ClassT` because that implementation ignores the `cls` parameter for `PyClassInit` (and would
7778
/// therefore be incorrect when instantiating subclasses).
7879
pub fn tp_new_resolver<ClassT, ValueT>(_: &ValueT) -> TpNewValueTypeResolver<ClassT, ValueT> {
79-
TpNewValueTypeResolver(ResolveToArbitraryObject(()), PhantomData)
80+
TpNewValueTypeResolver(TpNewTupleResolver(
81+
ResolveToArbitraryObject(()),
82+
PhantomData,
83+
))
8084
}
8185

8286
impl<ClassT, ValueT> core::ops::Deref for TpNewValueTypeResolver<ClassT, ValueT> {
87+
type Target = TpNewTupleResolver<ClassT, ValueT>;
88+
fn deref(&self) -> &Self::Target {
89+
&self.0
90+
}
91+
}
92+
93+
impl<ClassT, ValueT> core::ops::Deref for TpNewTupleResolver<ClassT, ValueT> {
8394
type Target = ResolveToArbitraryObject;
8495
fn deref(&self) -> &Self::Target {
8596
&self.0
@@ -100,6 +111,21 @@ where
100111
}
101112
}
102113

114+
impl<S, B> TpNewTupleResolver<S, (S, B)>
115+
where
116+
S: PyClass<BaseType = B>,
117+
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
118+
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
119+
{
120+
#[deprecated(
121+
since = "0.29.0",
122+
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
123+
)]
124+
pub fn resolve(&self, value: (S, B)) -> PyClassInitializer<S> {
125+
value.into()
126+
}
127+
}
128+
103129
/// All other conversions fall back to IntoPyObject via the deref implementation
104130
impl ResolveToArbitraryObject {
105131
pub fn resolve<ValueT>(&self, value: ValueT) -> ValueT {
@@ -169,14 +195,3 @@ where
169195
self.into()
170196
}
171197
}
172-
173-
impl<S, B> IntoPyClassInitializer<S> for (S, B)
174-
where
175-
S: PyClass<BaseType = B>,
176-
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
177-
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
178-
{
179-
fn into_pyclass_initializer(self) -> PyClassInitializer<S> {
180-
self.into()
181-
}
182-
}

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 core::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()

0 commit comments

Comments
 (0)