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}