From a83c31a3af001690b1cf2dc9bd7acf27cc890102 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. --- CHANGELOG.md | 10 +++++++++ src/conversions/path.rs | 2 +- src/types/any.rs | 23 +++++++++++++++++++-- src/types/typeobject.rs | 43 ++++++++++++++++++++++++++++++--------- tests/test_inheritance.rs | 18 ++++++++++++++++ 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c23dfbf..68670039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add implementations for `Py::as_ref()` and `Py::into_ref()` for `Py`, `Py` and `Py`. [#1682](https://github.com/PyO3/pyo3/pull/1682) - Add `PyTraceback` type to represent and format Python tracebacks. [#1977](https://github.com/PyO3/pyo3/pull/1977) +### Added + +- Add `PyType::is_subclass_of` and `PyAny::is_instance_of` which operate not on + a type known at compile-time but a run-time type object. [#1985](https://github.com/PyO3/pyo3/pull/1985) + ### Changed - `#[classattr]` constants with a known magic method name (which is lowercase) no longer trigger lint warnings expecting constants to be uppercase. [#1969](https://github.com/PyO3/pyo3/pull/1969) @@ -42,6 +47,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix mingw platform detection. [#1993](https://github.com/PyO3/pyo3/pull/1993) - Fix panic in `__get__` implementation when accessing descriptor on type object. [#1997](https://github.com/PyO3/pyo3/pull/1997) +### Removed + +- Remove `PyType::is_instance`, which is unintuitive; instead of `typ.is_instance(obj)`, you should + now use `obj.is_instance_of(typ)`. [#1985](https://github.com/PyO3/pyo3/pull/1985) + ## [0.15.0] - 2021-11-03 ### Packaging diff --git a/src/conversions/path.rs b/src/conversions/path.rs index 91a41666..552dd8c6 100644 --- a/src/conversions/path.rs +++ b/src/conversions/path.rs @@ -20,7 +20,7 @@ impl FromPyObject<'_> for PathBuf { let py = ob.py(); let pathlib = py.import("pathlib")?; let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?; - if pathlib_path.is_instance(ob)? { + if ob.is_instance_of(pathlib_path)? { let path_str = ob.call_method0("__str__")?; OsString::extract(path_str)? } else { diff --git a/src/types/any.rs b/src/types/any.rs index 8fc8640d..66b9ee76 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -667,9 +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) + self.is_instance_of(T::type_object(self.py())) + } + + /// Checks whether this object is an instance of type `typ`. + /// + /// This is equivalent to the Python expression `isinstance(self, typ)`. + pub fn is_instance_of(&self, typ: &PyType) -> PyResult { + let result = unsafe { ffi::PyObject_IsInstance(self.as_ptr(), typ.as_ptr()) }; + err::error_on_minusone(self.py(), result)?; + Ok(result == 1) } /// Returns a GIL marker constrained to the lifetime of this type. @@ -682,6 +692,7 @@ impl PyAny { #[cfg(test)] mod tests { use crate::{ + type_object::PyTypeObject, types::{IntoPyDict, PyList, PyLong, PyModule}, Python, ToPyObject, }; @@ -782,4 +793,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 09a1f8f2..f91967b3 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -40,25 +40,48 @@ 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, { - let result = - unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), T::type_object(self.py()).as_ptr()) }; - err::error_on_minusone(self.py(), result)?; - Ok(result == 1) + self.is_subclass_of(T::type_object(self.py())) } - /// Check whether `obj` is an instance of `self`. + /// Checks whether `self` is a subclass of `other`. /// - /// Equivalent to Python's `isinstance` function. - pub fn is_instance(&self, obj: &T) -> PyResult { - let result = unsafe { ffi::PyObject_IsInstance(obj.as_ptr(), self.as_ptr()) }; + /// 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) } } + +#[cfg(test)] +mod tests { + use crate::{ + type_object::PyTypeObject, + types::{PyBool, PyLong}, + Python, + }; + + #[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()); + }); + } +} diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 01b124b8..8dcdd9b6 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,