pyo3/types/
typeobject.rs

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