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.
This commit is contained in:
Georg Brandl 2021-11-11 20:06:42 +01:00 committed by David Hewitt
parent cc91a7c574
commit a83c31a3af
5 changed files with 83 additions and 13 deletions

View File

@ -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<PySequence>`, `Py<PyIterator>` and `Py<PyMapping>`. [#1682](https://github.com/PyO3/pyo3/pull/1682) - Add implementations for `Py::as_ref()` and `Py::into_ref()` for `Py<PySequence>`, `Py<PyIterator>` and `Py<PyMapping>`. [#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) - 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 ### 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) - `#[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 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) - 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 ## [0.15.0] - 2021-11-03
### Packaging ### Packaging

View File

@ -20,7 +20,7 @@ impl FromPyObject<'_> for PathBuf {
let py = ob.py(); let py = ob.py();
let pathlib = py.import("pathlib")?; let pathlib = py.import("pathlib")?;
let pathlib_path: &PyType = pathlib.getattr("Path")?.downcast()?; 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__")?; let path_str = ob.call_method0("__str__")?;
OsString::extract(path_str)? OsString::extract(path_str)?
} else { } else {

View File

@ -667,9 +667,19 @@ impl PyAny {
/// Checks whether this object is an instance of type `T`. /// 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<T: PyTypeObject>(&self) -> PyResult<bool> { pub fn is_instance<T: PyTypeObject>(&self) -> PyResult<bool> {
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<bool> {
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. /// Returns a GIL marker constrained to the lifetime of this type.
@ -682,6 +692,7 @@ impl PyAny {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{
type_object::PyTypeObject,
types::{IntoPyDict, PyList, PyLong, PyModule}, types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject, Python, ToPyObject,
}; };
@ -782,4 +793,12 @@ mod tests {
assert!(l.is_instance::<PyList>().unwrap()); assert!(l.is_instance::<PyList>().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());
});
}
} }

View File

@ -40,25 +40,48 @@ impl PyType {
self.getattr("__qualname__")?.extract() 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<T>(&self) -> PyResult<bool> pub fn is_subclass<T>(&self) -> PyResult<bool>
where where
T: PyTypeObject, T: PyTypeObject,
{ {
let result = self.is_subclass_of(T::type_object(self.py()))
unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), T::type_object(self.py()).as_ptr()) };
err::error_on_minusone(self.py(), result)?;
Ok(result == 1)
} }
/// Check whether `obj` is an instance of `self`. /// Checks whether `self` is a subclass of `other`.
/// ///
/// Equivalent to Python's `isinstance` function. /// Equivalent to the Python expression `issubclass(self, other)`.
pub fn is_instance<T: AsPyPointer>(&self, obj: &T) -> PyResult<bool> { pub fn is_subclass_of(&self, other: &PyType) -> PyResult<bool> {
let result = unsafe { ffi::PyObject_IsInstance(obj.as_ptr(), self.as_ptr()) }; let result = unsafe { ffi::PyObject_IsSubclass(self.as_ptr(), other.as_ptr()) };
err::error_on_minusone(self.py(), result)?; err::error_on_minusone(self.py(), result)?;
Ok(result == 1) 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::<PyLong>().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());
});
}
}

View File

@ -1,5 +1,6 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::py_run; use pyo3::py_run;
use pyo3::type_object::PyTypeObject;
use pyo3::types::IntoPyDict; use pyo3::types::IntoPyDict;
@ -102,6 +103,23 @@ fn mutation_fails() {
assert_eq!(&e.to_string(), "RuntimeError: Already borrowed") 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::<BaseClass>().unwrap());
assert!(sub_ty.is_subclass_of(base_ty).unwrap());
let obj = PyCell::new(py, SubClass::new()).unwrap();
assert!(obj.is_instance::<SubClass>().unwrap());
assert!(obj.is_instance::<BaseClass>().unwrap());
assert!(obj.is_instance_of(sub_ty).unwrap());
assert!(obj.is_instance_of(base_ty).unwrap());
}
#[pyclass(subclass)] #[pyclass(subclass)]
struct BaseClassWithResult { struct BaseClassWithResult {
_val: usize, _val: usize,