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