From 422975dee43530588b78d160446c70fda7335ff3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 11 Nov 2021 20:06:42 +0100 Subject: [PATCH] add PyType::is_subclass_of and PyAny::is_instance_of which get the type to check against as an arguments, as opposed to a compile-time generic type. --- src/types/any.rs | 19 ++++++++++++++- src/types/typeobject.rs | 50 ++++++++++++++++++++++++++++++++++++--- tests/test_inheritance.rs | 18 ++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/types/any.rs b/src/types/any.rs index 8fc8640d4e3..08ddc010246 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -667,11 +667,19 @@ impl PyAny { /// Checks whether this object is an instance of type `T`. /// - /// This is equivalent to the Python expression `isinstance(self, T)`. + /// This is equivalent to the Python expression `isinstance(self, T)`, + /// if the type `T` is known at compile time. pub fn is_instance(&self) -> PyResult { T::type_object(self.py()).is_instance(self) } + /// Checks whether this object is an instance of type `T`. + /// + /// This is equivalent to the Python expression `isinstance(self, typ)`. + pub fn is_instance_of(&self, typ: &PyType) -> PyResult { + typ.is_instance(self) + } + /// Returns a GIL marker constrained to the lifetime of this type. #[inline] pub fn py(&self) -> Python<'_> { @@ -682,6 +690,7 @@ impl PyAny { #[cfg(test)] mod tests { use crate::{ + type_object::PyTypeObject, types::{IntoPyDict, PyList, PyLong, PyModule}, Python, ToPyObject, }; @@ -782,4 +791,12 @@ mod tests { assert!(l.is_instance::().unwrap()); }); } + + #[test] + fn test_any_isinstance_of() { + Python::with_gil(|py| { + let l = vec![1u8, 2].to_object(py).into_ref(py); + assert!(l.is_instance_of(PyList::type_object(py)).unwrap()); + }); + } } diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 09a1f8f29ec..ef1bf589eb2 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -40,9 +40,10 @@ impl PyType { self.getattr("__qualname__")?.extract() } - /// Checks whether `self` is subclass of type `T`. + /// Checks whether `self` is a subclass of type `T`. /// - /// Equivalent to Python's `issubclass` function. + /// Equivalent to the Python expression `issubclass(self, T)`, if the type + /// `T` is known at compile time. pub fn is_subclass(&self) -> PyResult where T: PyTypeObject, @@ -53,12 +54,55 @@ impl PyType { Ok(result == 1) } + /// Checks whether `self` is a subclass of `other`. + /// + /// Equivalent to the Python expression `issubclass(self, other)`. + pub fn is_subclass_of(&self, other: &PyType) -> PyResult { + let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) }; + err::error_on_minusone(self.py(), result)?; + Ok(result == 1) + } + /// Check whether `obj` is an instance of `self`. /// - /// Equivalent to Python's `isinstance` function. + /// Equivalent to the Python expression `isinstance(obj, self)`. pub fn is_instance(&self, obj: &T) -> PyResult { let result = unsafe { ffi::PyObject_IsInstance(obj.as_ptr(), self.as_ptr()) }; err::error_on_minusone(self.py(), result)?; Ok(result == 1) } } + +#[cfg(test)] +mod tests { + use crate::{ + type_object::PyTypeObject, + types::{PyBool, PyLong}, + Python, ToPyObject, + }; + + #[test] + fn test_type_is_subclass() { + Python::with_gil(|py| { + assert!(PyBool::type_object(py).is_subclass::().unwrap()); + }); + } + + #[test] + fn test_type_is_subclass_of() { + Python::with_gil(|py| { + let bool_type = PyBool::type_object(py); + let long_type = PyLong::type_object(py); + assert!(bool_type.is_subclass_of(long_type).unwrap()); + }); + } + + #[test] + fn test_type_is_instance() { + Python::with_gil(|py| { + let bool_type = PyBool::type_object(py); + let obj = false.to_object(py); + assert!(bool_type.is_instance(&obj).unwrap()); + }); + } +} diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 01b124b8be9..8dcdd9b6ac6 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -1,5 +1,6 @@ use pyo3::prelude::*; use pyo3::py_run; +use pyo3::type_object::PyTypeObject; use pyo3::types::IntoPyDict; @@ -102,6 +103,23 @@ fn mutation_fails() { assert_eq!(&e.to_string(), "RuntimeError: Already borrowed") } +#[test] +fn is_subclass_and_is_instance() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + let sub_ty = SubClass::type_object(py); + let base_ty = BaseClass::type_object(py); + assert!(sub_ty.is_subclass::().unwrap()); + assert!(sub_ty.is_subclass_of(base_ty).unwrap()); + + let obj = PyCell::new(py, SubClass::new()).unwrap(); + assert!(obj.is_instance::().unwrap()); + assert!(obj.is_instance::().unwrap()); + assert!(obj.is_instance_of(sub_ty).unwrap()); + assert!(obj.is_instance_of(base_ty).unwrap()); +} + #[pyclass(subclass)] struct BaseClassWithResult { _val: usize,