Skip to content

Commit

Permalink
add PyType::is_subclass_of and PyAny::is_instance_of
Browse files Browse the repository at this point in the history
which get the type to check against as an arguments,
as opposed to a compile-time generic type.
  • Loading branch information
birkenfeld committed Nov 11, 2021
1 parent 2009182 commit 422975d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 4 deletions.
19 changes: 18 additions & 1 deletion src/types/any.rs
Expand Up @@ -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<T: PyTypeObject>(&self) -> PyResult<bool> {
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<bool> {
typ.is_instance(self)
}

/// Returns a GIL marker constrained to the lifetime of this type.
#[inline]
pub fn py(&self) -> Python<'_> {
Expand All @@ -682,6 +690,7 @@ impl PyAny {
#[cfg(test)]
mod tests {
use crate::{
type_object::PyTypeObject,
types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject,
};
Expand Down Expand Up @@ -782,4 +791,12 @@ mod tests {
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());
});
}
}
50 changes: 47 additions & 3 deletions src/types/typeobject.rs
Expand Up @@ -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<T>(&self) -> PyResult<bool>
where
T: PyTypeObject,
Expand All @@ -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<bool> {
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<T: AsPyPointer>(&self, obj: &T) -> PyResult<bool> {
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::<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());
});
}

#[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());
});
}
}
18 changes: 18 additions & 0 deletions tests/test_inheritance.rs
@@ -1,5 +1,6 @@
use pyo3::prelude::*;
use pyo3::py_run;
use pyo3::type_object::PyTypeObject;

use pyo3::types::IntoPyDict;

Expand Down Expand Up @@ -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::<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)]
struct BaseClassWithResult {
_val: usize,
Expand Down

0 comments on commit 422975d

Please sign in to comment.