Skip to main content

pyo3/types/
typeobject.rs

1use crate::err::{self, PyResult};
2use crate::instance::Borrowed;
3#[cfg(not(Py_3_13))]
4use crate::pybacked::PyBackedStr;
5#[cfg(any(Py_LIMITED_API, PyPy, not(Py_3_13)))]
6use crate::types::any::PyAnyMethods;
7use crate::types::PyTuple;
8use crate::{ffi, Bound, PyAny, PyTypeInfo, Python};
9#[cfg(RustPython)]
10use crate::{sync::PyOnceLock, Py};
11
12use super::PyString;
13
14/// Represents a reference to a Python `type` object.
15///
16/// Values of this type are accessed via PyO3's smart pointers, e.g. as
17/// [`Py<PyType>`][crate::Py] or [`Bound<'py, PyType>`][Bound].
18///
19/// For APIs available on `type` objects, see the [`PyTypeMethods`] trait which is implemented for
20/// [`Bound<'py, PyType>`][Bound].
21#[repr(transparent)]
22pub struct PyType(PyAny);
23
24#[cfg(not(RustPython))]
25pyobject_native_type_core!(PyType, pyobject_native_static_type_object!(ffi::PyType_Type), "builtins", "type", #checkfunction=ffi::PyType_Check);
26
27#[cfg(RustPython)]
28pyobject_native_type_core!(
29    PyType,
30    |py| {
31        static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
32        TYPE.import(py, "builtins", "type").unwrap().as_type_ptr()
33    },
34    "builtins",
35    "type",
36    #checkfunction=ffi::PyType_Check
37);
38
39impl PyType {
40    /// Creates a new type object.
41    #[inline]
42    pub fn new<T: PyTypeInfo>(py: Python<'_>) -> Bound<'_, PyType> {
43        T::type_object(py)
44    }
45
46    /// Converts the given FFI pointer into `Bound<PyType>`, to use in safe code.
47    ///
48    /// The function creates a new reference from the given pointer, and returns
49    /// it as a `Bound<PyType>`.
50    ///
51    /// # Safety
52    /// - The pointer must be a valid non-null reference to a `PyTypeObject`
53    #[inline]
54    pub unsafe fn from_borrowed_type_ptr(
55        py: Python<'_>,
56        p: *mut ffi::PyTypeObject,
57    ) -> Bound<'_, PyType> {
58        unsafe {
59            Borrowed::from_ptr_unchecked(py, p.cast())
60                .cast_unchecked()
61                .to_owned()
62        }
63    }
64}
65
66/// Implementation of functionality for [`PyType`].
67///
68/// These methods are defined for the `Bound<'py, PyType>` smart pointer, so to use method call
69/// syntax these methods are separated into a trait, because stable Rust does not yet support
70/// `arbitrary_self_types`.
71#[doc(alias = "PyType")]
72pub trait PyTypeMethods<'py>: crate::sealed::Sealed {
73    /// Retrieves the underlying FFI pointer associated with this Python object.
74    fn as_type_ptr(&self) -> *mut ffi::PyTypeObject;
75
76    /// Gets the name of the `PyType`. Equivalent to `self.__name__` in Python.
77    fn name(&self) -> PyResult<Bound<'py, PyString>>;
78
79    /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`.
80    /// Equivalent to `self.__qualname__` in Python.
81    fn qualname(&self) -> PyResult<Bound<'py, PyString>>;
82
83    /// Gets the name of the module defining the `PyType`.
84    fn module(&self) -> PyResult<Bound<'py, PyString>>;
85
86    /// Gets the [fully qualified name](https://peps.python.org/pep-0737/#add-pytype-getfullyqualifiedname-function) of the `PyType`.
87    fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>>;
88
89    /// Checks whether `self` is a subclass of `other`.
90    ///
91    /// Equivalent to the Python expression `issubclass(self, other)`.
92    fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool>;
93
94    /// Checks whether `self` is a subclass of type `T`.
95    ///
96    /// Equivalent to the Python expression `issubclass(self, T)`, if the type
97    /// `T` is known at compile time.
98    fn is_subclass_of<T>(&self) -> PyResult<bool>
99    where
100        T: PyTypeInfo;
101
102    /// Return the method resolution order for this type.
103    ///
104    /// Equivalent to the Python expression `self.__mro__`.
105    fn mro(&self) -> Bound<'py, PyTuple>;
106
107    /// Return Python bases
108    ///
109    /// Equivalent to the Python expression `self.__bases__`.
110    fn bases(&self) -> Bound<'py, PyTuple>;
111}
112
113impl<'py> PyTypeMethods<'py> for Bound<'py, PyType> {
114    /// Retrieves the underlying FFI pointer associated with this Python object.
115    #[inline]
116    fn as_type_ptr(&self) -> *mut ffi::PyTypeObject {
117        self.as_ptr() as *mut ffi::PyTypeObject
118    }
119
120    /// Gets the name of the `PyType`.
121    fn name(&self) -> PyResult<Bound<'py, PyString>> {
122        #[cfg(not(Py_3_11))]
123        let name = self.getattr(intern!(self.py(), "__name__"))?.cast_into()?;
124
125        #[cfg(Py_3_11)]
126        let name = unsafe {
127            use crate::ffi_ptr_ext::FfiPtrExt;
128            ffi::PyType_GetName(self.as_type_ptr())
129                .assume_owned_or_err(self.py())?
130                // SAFETY: setting `__name__` from Python is required to be a `str`
131                .cast_into_unchecked()
132        };
133
134        Ok(name)
135    }
136
137    /// Gets the [qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`.
138    fn qualname(&self) -> PyResult<Bound<'py, PyString>> {
139        #[cfg(not(Py_3_11))]
140        let name = self
141            .getattr(intern!(self.py(), "__qualname__"))?
142            .cast_into()?;
143
144        #[cfg(Py_3_11)]
145        let name = unsafe {
146            use crate::ffi_ptr_ext::FfiPtrExt;
147            ffi::PyType_GetQualName(self.as_type_ptr())
148                .assume_owned_or_err(self.py())?
149                // SAFETY: setting `__qualname__` from Python is required to be a `str`
150                .cast_into_unchecked()
151        };
152
153        Ok(name)
154    }
155
156    /// Gets the name of the module defining the `PyType`.
157    fn module(&self) -> PyResult<Bound<'py, PyString>> {
158        #[cfg(not(Py_3_13))]
159        let name = self.getattr(intern!(self.py(), "__module__"))?;
160
161        #[cfg(Py_3_13)]
162        let name = unsafe {
163            use crate::ffi_ptr_ext::FfiPtrExt;
164            ffi::PyType_GetModuleName(self.as_type_ptr()).assume_owned_or_err(self.py())?
165        };
166
167        // `__module__` is never guaranteed to be a `str`
168        name.cast_into().map_err(Into::into)
169    }
170
171    /// Gets the [fully qualified name](https://docs.python.org/3/glossary.html#term-qualified-name) of the `PyType`.
172    fn fully_qualified_name(&self) -> PyResult<Bound<'py, PyString>> {
173        #[cfg(not(Py_3_13))]
174        let name = {
175            let module = self.getattr(intern!(self.py(), "__module__"))?;
176            let qualname = self.getattr(intern!(self.py(), "__qualname__"))?;
177
178            let module_str = module.extract::<PyBackedStr>()?;
179            if module_str == "builtins" || module_str == "__main__" {
180                qualname.cast_into()?
181            } else {
182                PyString::new(self.py(), &format!("{module}.{qualname}"))
183            }
184        };
185
186        #[cfg(Py_3_13)]
187        let name = unsafe {
188            use crate::ffi_ptr_ext::FfiPtrExt;
189            ffi::PyType_GetFullyQualifiedName(self.as_type_ptr())
190                .assume_owned_or_err(self.py())?
191                .cast_into_unchecked()
192        };
193
194        Ok(name)
195    }
196
197    /// Checks whether `self` is a subclass of `other`.
198    ///
199    /// Equivalent to the Python expression `issubclass(self, other)`.
200    fn is_subclass(&self, other: &Bound<'_, PyAny>) -> PyResult<bool> {
201        let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
202        err::error_on_minusone(self.py(), result)?;
203        Ok(result == 1)
204    }
205
206    /// Checks whether `self` is a subclass of type `T`.
207    ///
208    /// Equivalent to the Python expression `issubclass(self, T)`, if the type
209    /// `T` is known at compile time.
210    fn is_subclass_of<T>(&self) -> PyResult<bool>
211    where
212        T: PyTypeInfo,
213    {
214        self.is_subclass(&T::type_object(self.py()))
215    }
216
217    fn mro(&self) -> Bound<'py, PyTuple> {
218        #[cfg(any(Py_LIMITED_API, PyPy))]
219        let mro = self
220            .getattr(intern!(self.py(), "__mro__"))
221            .expect("Cannot get `__mro__` from object.")
222            .extract()
223            .expect("Unexpected type in `__mro__` attribute.");
224
225        #[cfg(not(any(Py_LIMITED_API, PyPy)))]
226        let mro = unsafe {
227            use crate::ffi_ptr_ext::FfiPtrExt;
228            (*self.as_type_ptr())
229                .tp_mro
230                .assume_borrowed(self.py())
231                .to_owned()
232                .cast_into_unchecked()
233        };
234
235        mro
236    }
237
238    fn bases(&self) -> Bound<'py, PyTuple> {
239        #[cfg(any(Py_LIMITED_API, PyPy))]
240        let bases = self
241            .getattr(intern!(self.py(), "__bases__"))
242            .expect("Cannot get `__bases__` from object.")
243            .extract()
244            .expect("Unexpected type in `__bases__` attribute.");
245
246        #[cfg(not(any(Py_LIMITED_API, PyPy)))]
247        let bases = unsafe {
248            use crate::ffi_ptr_ext::FfiPtrExt;
249            (*self.as_type_ptr())
250                .tp_bases
251                .assume_borrowed(self.py())
252                .to_owned()
253                .cast_into_unchecked()
254        };
255
256        bases
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use crate::test_utils::generate_unique_module_name;
263    use crate::types::{PyAnyMethods, PyBool, PyInt, PyModule, PyTuple, PyType, PyTypeMethods};
264    use crate::PyAny;
265    use crate::Python;
266    use pyo3_ffi::c_str;
267
268    #[test]
269    fn test_type_is_subclass() {
270        Python::attach(|py| {
271            let bool_type = py.get_type::<PyBool>();
272            let long_type = py.get_type::<PyInt>();
273            assert!(bool_type.is_subclass(&long_type).unwrap());
274        });
275    }
276
277    #[test]
278    fn test_type_is_subclass_of() {
279        Python::attach(|py| {
280            assert!(py.get_type::<PyBool>().is_subclass_of::<PyInt>().unwrap());
281        });
282    }
283
284    #[test]
285    fn test_mro() {
286        Python::attach(|py| {
287            assert!(py
288                .get_type::<PyBool>()
289                .mro()
290                .eq(PyTuple::new(
291                    py,
292                    [
293                        py.get_type::<PyBool>(),
294                        py.get_type::<PyInt>(),
295                        py.get_type::<PyAny>()
296                    ]
297                )
298                .unwrap())
299                .unwrap());
300        });
301    }
302
303    #[test]
304    fn test_bases_bool() {
305        Python::attach(|py| {
306            assert!(py
307                .get_type::<PyBool>()
308                .bases()
309                .eq(PyTuple::new(py, [py.get_type::<PyInt>()]).unwrap())
310                .unwrap());
311        });
312    }
313
314    #[test]
315    fn test_bases_object() {
316        Python::attach(|py| {
317            assert!(py
318                .get_type::<PyAny>()
319                .bases()
320                .eq(PyTuple::empty(py))
321                .unwrap());
322        });
323    }
324
325    #[test]
326    fn test_type_names_standard() {
327        Python::attach(|py| {
328            let module_name = generate_unique_module_name("test_module");
329            let module = PyModule::from_code(
330                py,
331                cr#"
332class MyClass:
333    pass
334"#,
335                c_str!(file!()),
336                &module_name,
337            )
338            .expect("module create failed");
339
340            let my_class = module.getattr("MyClass").unwrap();
341            let my_class_type = my_class.cast_into::<PyType>().unwrap();
342            assert_eq!(my_class_type.name().unwrap(), "MyClass");
343            assert_eq!(my_class_type.qualname().unwrap(), "MyClass");
344            let module_name = module_name.to_str().unwrap();
345            let qualname = format!("{module_name}.MyClass");
346            assert_eq!(my_class_type.module().unwrap(), module_name);
347            assert_eq!(
348                my_class_type.fully_qualified_name().unwrap(),
349                qualname.as_str()
350            );
351        });
352    }
353
354    #[test]
355    fn test_type_names_builtin() {
356        Python::attach(|py| {
357            let bool_type = py.get_type::<PyBool>();
358            assert_eq!(bool_type.name().unwrap(), "bool");
359            assert_eq!(bool_type.qualname().unwrap(), "bool");
360            assert_eq!(bool_type.module().unwrap(), "builtins");
361            assert_eq!(bool_type.fully_qualified_name().unwrap(), "bool");
362        });
363    }
364
365    #[test]
366    fn test_type_names_nested() {
367        Python::attach(|py| {
368            let module_name = generate_unique_module_name("test_module");
369            let module = PyModule::from_code(
370                py,
371                cr#"
372class OuterClass:
373    class InnerClass:
374        pass
375"#,
376                c_str!(file!()),
377                &module_name,
378            )
379            .expect("module create failed");
380
381            let outer_class = module.getattr("OuterClass").unwrap();
382            let inner_class = outer_class.getattr("InnerClass").unwrap();
383            let inner_class_type = inner_class.cast_into::<PyType>().unwrap();
384            assert_eq!(inner_class_type.name().unwrap(), "InnerClass");
385            assert_eq!(
386                inner_class_type.qualname().unwrap(),
387                "OuterClass.InnerClass"
388            );
389            let module_name = module_name.to_str().unwrap();
390            let qualname = format!("{module_name}.OuterClass.InnerClass");
391            assert_eq!(inner_class_type.module().unwrap(), module_name);
392            assert_eq!(
393                inner_class_type.fully_qualified_name().unwrap(),
394                qualname.as_str()
395            );
396        });
397    }
398}