Skip to main content

pyo3/
pyclass_init.rs

1//! Contains initialization utilities for `#[pyclass]`.
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::impl_::pyclass::{PyClassBaseType, PyClassImpl};
4use crate::impl_::pyclass_init::{PyNativeTypeInitializer, PyObjectInit};
5use crate::pycell::impl_::PyClassObjectLayout;
6use crate::{ffi, Bound, PyClass, PyResult, Python};
7use crate::{ffi::PyTypeObject, pycell::impl_::PyClassObjectContents};
8use std::marker::PhantomData;
9
10/// Initializer for our `#[pyclass]` system.
11///
12/// You can use this type to initialize complicatedly nested `#[pyclass]`.
13///
14/// # Examples
15///
16/// ```
17/// # use pyo3::prelude::*;
18/// # use pyo3::py_run;
19/// #[pyclass(subclass)]
20/// struct BaseClass {
21///     #[pyo3(get)]
22///     basename: &'static str,
23/// }
24/// #[pyclass(extends=BaseClass, subclass)]
25/// struct SubClass {
26///     #[pyo3(get)]
27///     subname: &'static str,
28/// }
29/// #[pyclass(extends=SubClass)]
30/// struct SubSubClass {
31///     #[pyo3(get)]
32///     subsubname: &'static str,
33/// }
34///
35/// #[pymethods]
36/// impl SubSubClass {
37///     #[new]
38///     fn new() -> PyClassInitializer<Self> {
39///         PyClassInitializer::from(BaseClass { basename: "base" })
40///             .add_subclass(SubClass { subname: "sub" })
41///             .add_subclass(SubSubClass {
42///                 subsubname: "subsub",
43///             })
44///     }
45/// }
46/// Python::attach(|py| {
47///     let typeobj = py.get_type::<SubSubClass>();
48///     let sub_sub_class = typeobj.call((), None).unwrap();
49///     py_run!(
50///         py,
51///         sub_sub_class,
52///         r#"
53///  assert sub_sub_class.basename == 'base'
54///  assert sub_sub_class.subname == 'sub'
55///  assert sub_sub_class.subsubname == 'subsub'"#
56///     );
57/// });
58/// ```
59pub struct PyClassInitializer<T: PyClass> {
60    init: T,
61    super_init: <T::BaseType as PyClassBaseType>::Initializer,
62}
63
64impl<T: PyClass> PyClassInitializer<T> {
65    /// Constructs a new initializer from value `T` and base class' initializer.
66    ///
67    /// It is recommended to use `add_subclass` instead of this method for most usage.
68    #[track_caller]
69    #[inline]
70    pub fn new(init: T, super_init: <T::BaseType as PyClassBaseType>::Initializer) -> Self {
71        Self { init, super_init }
72    }
73
74    /// Constructs a new initializer from an initializer for the base class.
75    ///
76    /// # Examples
77    /// ```
78    /// use pyo3::prelude::*;
79    ///
80    /// #[pyclass(subclass)]
81    /// struct BaseClass {
82    ///     #[pyo3(get)]
83    ///     value: i32,
84    /// }
85    ///
86    /// impl BaseClass {
87    ///     fn new(value: i32) -> PyResult<Self> {
88    ///         Ok(Self { value })
89    ///     }
90    /// }
91    ///
92    /// #[pyclass(extends=BaseClass)]
93    /// struct SubClass {}
94    ///
95    /// #[pymethods]
96    /// impl SubClass {
97    ///     #[new]
98    ///     fn new(value: i32) -> PyResult<PyClassInitializer<Self>> {
99    ///         let base_init = PyClassInitializer::from(BaseClass::new(value)?);
100    ///         Ok(base_init.add_subclass(SubClass {}))
101    ///     }
102    /// }
103    ///
104    /// fn main() -> PyResult<()> {
105    ///     Python::attach(|py| {
106    ///         let m = PyModule::new(py, "example")?;
107    ///         m.add_class::<SubClass>()?;
108    ///         m.add_class::<BaseClass>()?;
109    ///
110    ///         let instance = m.getattr("SubClass")?.call1((92,))?;
111    ///
112    ///         // `SubClass` does not have a `value` attribute, but `BaseClass` does.
113    ///         let n = instance.getattr("value")?.extract::<i32>()?;
114    ///         assert_eq!(n, 92);
115    ///
116    ///         Ok(())
117    ///     })
118    /// }
119    /// ```
120    #[track_caller]
121    #[inline]
122    pub fn add_subclass<S>(self, subclass_value: S) -> PyClassInitializer<S>
123    where
124        T: PyClassBaseType<Initializer = Self>,
125        S: PyClass<BaseType = T>,
126    {
127        PyClassInitializer::new(subclass_value, self)
128    }
129
130    /// Creates a new class object and initializes it.
131    pub(crate) fn create_class_object(self, py: Python<'_>) -> PyResult<Bound<'_, T>>
132    where
133        T: PyClass,
134    {
135        unsafe { self.create_class_object_of_type(py, T::type_object_raw(py)) }
136    }
137
138    /// Creates a new class object and initializes it given a typeobject `subtype`.
139    ///
140    /// # Safety
141    /// `subtype` must be a valid pointer to the type object of T or a subclass.
142    pub(crate) unsafe fn create_class_object_of_type(
143        self,
144        py: Python<'_>,
145        target_type: *mut crate::ffi::PyTypeObject,
146    ) -> PyResult<Bound<'_, T>>
147    where
148        T: PyClass,
149    {
150        let obj = unsafe { self.super_init.into_new_object(py, target_type)? };
151
152        // SAFETY: `obj` is constructed using `T::Layout` but has not been initialized yet
153        let contents = unsafe { <T as PyClassImpl>::Layout::contents_uninit(obj) };
154        // SAFETY: `contents` is a non-null pointer to the space allocated for our
155        // `PyClassObjectContents` (either statically in Rust or dynamically by Python)
156        unsafe { (*contents).write(PyClassObjectContents::new(self.init)) };
157
158        // Safety: obj is a valid pointer to an object of type `target_type`, which` is a known
159        // subclass of `T`
160        Ok(unsafe { obj.assume_owned(py).cast_into_unchecked() })
161    }
162}
163
164impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
165    unsafe fn into_new_object(
166        self,
167        py: Python<'_>,
168        subtype: *mut PyTypeObject,
169    ) -> PyResult<*mut ffi::PyObject> {
170        unsafe {
171            self.create_class_object_of_type(py, subtype)
172                .map(Bound::into_ptr)
173        }
174    }
175}
176
177impl<T> From<T> for PyClassInitializer<T>
178where
179    T: PyClass,
180    T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
181{
182    #[inline]
183    fn from(value: T) -> PyClassInitializer<T> {
184        Self::new(value, PyNativeTypeInitializer(PhantomData))
185    }
186}
187
188impl<S, B> From<(S, B)> for PyClassInitializer<S>
189where
190    S: PyClass<BaseType = B>,
191    B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
192    B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
193{
194    #[track_caller]
195    #[inline]
196    fn from(sub_and_base: (S, B)) -> PyClassInitializer<S> {
197        let (sub, base) = sub_and_base;
198        PyClassInitializer::from(base).add_subclass(sub)
199    }
200}