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

### 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)
}
}
```

## from 0.27.* to 0.28

### Default to supporting free-threaded Python
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.
9 changes: 7 additions & 2 deletions pyo3-macros-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1483,14 +1483,19 @@ fn generate_method_body(
#pyo3_path::impl_::pymethods::tp_new_impl::<_, #cls>(#py, #initializer, #slf)
};

let value = syn::Ident::new("value", Span::call_site());
let resolver = quote_spanned! { *output_span =>
#pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&#value).resolve(#value);
};

let body = quote! {
#text_signature_impl
#warnings
#arg_convert

let result = #call;
let value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?;
let #initializer = #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&value).resolve(value);
let #value = #pyo3_path::impl_::wrap::OkWrapper::new(&result).ok_wrap(result)?;
let #initializer = #resolver;
unsafe { #conversion }
};
(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
39 changes: 1 addition & 38 deletions src/impl_/pyclass/probes.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use core::marker::PhantomData;

use crate::conversion::IntoPyObject;
use crate::impl_::pyclass::PyClassBaseType;
use crate::impl_::pyclass_init::PyNativeTypeInitializer;
use crate::{FromPyObject, Py, PyClass, PyClassInitializer};
use crate::{FromPyObject, Py};

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

probe!(IsPyClass);

impl<T> IsPyClass<T>
where
T: PyClass,
{
pub const VALUE: bool = true;
}

impl<T, E> IsPyClass<Result<T, E>>
where
T: PyClass,
{
pub const VALUE: bool = true;
}

probe!(IsInitializerTuple);

impl<S, B> IsInitializerTuple<(S, B)>
where
S: PyClass<BaseType = B>,
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
pub const VALUE: bool = true;
}
impl<S, B, E> IsInitializerTuple<Result<(S, B), E>>
where
S: PyClass<BaseType = B>,
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
pub const VALUE: bool = true;
}

#[cfg(test)]
macro_rules! value_of {
($probe:ident, $ty:ty) => {{
Expand Down
13 changes: 9 additions & 4 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,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 @@ -735,7 +736,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 @@ -2849,6 +2851,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 @@ -2929,7 +2932,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 @@ -2939,7 +2943,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
41 changes: 28 additions & 13 deletions src/internal/pyclass_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
}
}

pub struct TpNewValueTypeResolver<ClassT, ValueT>(
pub struct TpNewValueTypeResolver<ClassT, ValueT>(TpNewTupleResolver<ClassT, ValueT>);
pub struct TpNewTupleResolver<ClassT, ValueT>(
ResolveToArbitraryObject,
PhantomData<(ClassT, ValueT)>,
);
Expand All @@ -76,10 +77,20 @@ pub struct ResolveToArbitraryObject(());
/// `ClassT` because that implementation ignores the `cls` parameter for `PyClassInit` (and would
/// therefore be incorrect when instantiating subclasses).
pub fn tp_new_resolver<ClassT, ValueT>(_: &ValueT) -> TpNewValueTypeResolver<ClassT, ValueT> {
TpNewValueTypeResolver(ResolveToArbitraryObject(()), PhantomData)
TpNewValueTypeResolver(TpNewTupleResolver(
ResolveToArbitraryObject(()),
PhantomData,
))
}

impl<ClassT, ValueT> core::ops::Deref for TpNewValueTypeResolver<ClassT, ValueT> {
type Target = TpNewTupleResolver<ClassT, ValueT>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<ClassT, ValueT> core::ops::Deref for TpNewTupleResolver<ClassT, ValueT> {
type Target = ResolveToArbitraryObject;
fn deref(&self) -> &Self::Target {
&self.0
Expand All @@ -100,6 +111,21 @@ where
}
}

impl<S, B> TpNewTupleResolver<S, (S, B)>
where
S: PyClass<BaseType = B>,
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
#[deprecated(
since = "0.29.0",
note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead."
)]
pub fn resolve(&self, value: (S, B)) -> PyClassInitializer<S> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that this didn't even require an extra layer of deref specialization!

value.into()
}
}

/// All other conversions fall back to IntoPyObject via the deref implementation
impl ResolveToArbitraryObject {
pub fn resolve<ValueT>(&self, value: ValueT) -> ValueT {
Expand Down Expand Up @@ -169,14 +195,3 @@ where
self.into()
}
}

impl<S, B> IntoPyClassInitializer<S> for (S, B)
where
S: PyClass<BaseType = B>,
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
fn into_pyclass_initializer(self) -> PyClassInitializer<S> {
self.into()
}
}
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 core::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
Loading
Loading