Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add PyType::is_subclass_of and PyAny::is_instance_of #1985

Merged
merged 2 commits into from Nov 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Expand Up @@ -19,8 +19,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add `Py::setattr` method. [#2009](https://github.com/PyO3/pyo3/pull/2009)

## Removed
### Changed

- `PyType::is_subclass`, `PyErr::is_instance` and `PyAny::is_instance` now operate run-time type object instead of a type known at compile-time. The old behavior is still available as `PyType::is_subclass_of`, `PyErr::is_instance_of` and `PyAny::is_instance_of`. [#1985](https://github.com/PyO3/pyo3/pull/1985)

### Removed

- Remove `PyType::is_instance`, which is unintuitive; instead of `typ.is_instance(obj)`, use `obj.is_instance(typ)`. [#1985](https://github.com/PyO3/pyo3/pull/1985)
- Remove all functionality deprecated in PyO3 0.14. [#2007](https://github.com/PyO3/pyo3/pull/2007)

## [0.15.1] - 2021-11-19
Expand Down
2 changes: 1 addition & 1 deletion guide/src/conversions/traits.md
Expand Up @@ -413,7 +413,7 @@ enum RustyEnum {
# {
# let thing = b"foo".to_object(py);
# let error = thing.extract::<RustyEnum>(py).unwrap_err();
# assert!(error.is_instance::<pyo3::exceptions::PyTypeError>(py));
# assert!(error.is_instance_of::<pyo3::exceptions::PyTypeError>(py));
# }
#
# Ok(())
Expand Down
13 changes: 7 additions & 6 deletions guide/src/ecosystem/async-await.md
Expand Up @@ -536,12 +536,13 @@ fn main() -> PyResult<()> {
pyo3_asyncio::async_std::run(py, async move {
// verify that we are on a uvloop.Loop
Python::with_gil(|py| -> PyResult<()> {
assert!(uvloop
.as_ref(py)
.getattr("Loop")?
.downcast::<PyType>()
.unwrap()
.is_instance(pyo3_asyncio::async_std::get_current_loop(py)?)?);
assert!(pyo3_asyncio::async_std::get_current_loop(py)?.is_instance(
uvloop
.as_ref(py)
.getattr("Loop")?
.downcast::<PyType>()
.unwrap()
)?);
Ok(())
})?;

Expand Down
26 changes: 12 additions & 14 deletions guide/src/exception.md
Expand Up @@ -100,21 +100,19 @@ PyErr::from_instance(py, err).restore(py);
## Checking exception types

Python has an [`isinstance`](https://docs.python.org/3/library/functions.html#isinstance) method to check an object's type.
In PyO3 every native type has access to the [`PyAny::is_instance`] method which does the same thing.
In PyO3 every object has the [`PyAny::is_instance`] and [`PyAny::is_instance_of`] methods which do the same thing.

```rust
use pyo3::Python;
use pyo3::types::{PyBool, PyList};

Python::with_gil(|py| {
assert!(PyBool::new(py, true).is_instance::<PyBool>().unwrap());
assert!(PyBool::new(py, true).is_instance_of::<PyBool>().unwrap());
let list = PyList::new(py, &[1, 2, 3, 4]);
assert!(!list.is_instance::<PyBool>().unwrap());
assert!(list.is_instance::<PyList>().unwrap());
assert!(!list.is_instance_of::<PyBool>().unwrap());
assert!(list.is_instance_of::<PyList>().unwrap());
});
```
[`PyAny::is_instance`] calls the underlying [`PyType::is_instance`]({{#PYO3_DOCS_URL}}/pyo3/types/struct.PyType.html#method.is_instance)
method to do the actual work.

To check the type of an exception, you can similarly do:

Expand All @@ -123,7 +121,7 @@ To check the type of an exception, you can similarly do:
# use pyo3::prelude::*;
# Python::with_gil(|py| {
# let err = PyTypeError::new_err(());
err.is_instance::<PyTypeError>(py);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, just been reminded from this that we probably need to make equivalent changes to PyErr?

Either that or we could deprecate Err::is_instance() too (and advise just using err.pvalue(py).is_instance_of::<PyTypeError>()).

EDIT: Sorry just seen you have actually fixed this! Was worried the above diff was from a blanket rename.

err.is_instance_of::<PyTypeError>(py);
# });
```

Expand Down Expand Up @@ -184,7 +182,7 @@ fn main() {
Python::with_gil(|py| {
let fun = pyo3::wrap_pyfunction!(connect, py).unwrap();
let err = fun.call1(("0.0.0.0",)).unwrap_err();
assert!(err.is_instance::<PyOSError>(py));
assert!(err.is_instance_of::<PyOSError>(py));
});
}
```
Expand All @@ -201,21 +199,21 @@ fn parse_int(s: String) -> PyResult<usize> {
}
#
# use pyo3::exceptions::PyValueError;
#
#
# fn main() {
# Python::with_gil(|py| {
# assert_eq!(parse_int(String::from("1")).unwrap(), 1);
# assert_eq!(parse_int(String::from("1337")).unwrap(), 1337);
#
#
# assert!(parse_int(String::from("-1"))
# .unwrap_err()
# .is_instance::<PyValueError>(py));
# .is_instance_of::<PyValueError>(py));
# assert!(parse_int(String::from("foo"))
# .unwrap_err()
# .is_instance::<PyValueError>(py));
# .is_instance_of::<PyValueError>(py));
# assert!(parse_int(String::from("13.37"))
# .unwrap_err()
# .is_instance::<PyValueError>(py));
# .is_instance_of::<PyValueError>(py));
# })
# }
```
Expand Down Expand Up @@ -257,5 +255,5 @@ defines exceptions for several standard library modules.
[`PyErr`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html
[`PyResult`]: {{#PYO3_DOCS_URL}}/pyo3/type.PyResult.html
[`PyErr::from_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyErr.html#method.from_instance
[`Python::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.Python.html#method.is_instance
[`PyAny::is_instance`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance
[`PyAny::is_instance_of`]: {{#PYO3_DOCS_URL}}/pyo3/struct.PyAny.html#method.is_instance_of
2 changes: 1 addition & 1 deletion src/conversions/path.rs
Expand Up @@ -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(pathlib_path)? {
let path_str = ob.call_method0("__str__")?;
OsString::extract(path_str)?
} else {
Expand Down
21 changes: 12 additions & 9 deletions src/err/mod.rs
Expand Up @@ -180,7 +180,7 @@ impl PyErr {
///
/// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",));
/// assert!(err.is_instance::<PyTypeError>(py));
/// assert!(err.is_instance_of::<PyTypeError>(py));
/// assert_eq!(err.pvalue(py).to_string(), "some type error");
/// });
/// ```
Expand Down Expand Up @@ -366,13 +366,16 @@ impl PyErr {
}

/// Returns true if the current exception is instance of `T`.
pub fn is_instance<T>(&self, py: Python) -> bool
pub fn is_instance(&self, py: Python, typ: &PyType) -> bool {
unsafe { ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), typ.as_ptr()) != 0 }
}

/// Returns true if the current exception is instance of `T`.
pub fn is_instance_of<T>(&self, py: Python) -> bool
where
T: PyTypeObject,
{
unsafe {
ffi::PyErr_GivenExceptionMatches(self.ptype_ptr(py), T::type_object(py).as_ptr()) != 0
}
self.is_instance(py, T::type_object(py))
}

/// Retrieves the exception instance for this error.
Expand Down Expand Up @@ -612,11 +615,11 @@ mod tests {
fn set_valueerror() {
Python::with_gil(|py| {
let err: PyErr = exceptions::PyValueError::new_err("some exception message");
assert!(err.is_instance::<exceptions::PyValueError>(py));
assert!(err.is_instance_of::<exceptions::PyValueError>(py));
err.restore(py);
assert!(PyErr::occurred(py));
let err = PyErr::fetch(py);
assert!(err.is_instance::<exceptions::PyValueError>(py));
assert!(err.is_instance_of::<exceptions::PyValueError>(py));
assert_eq!(err.to_string(), "ValueError: some exception message");
})
}
Expand All @@ -625,10 +628,10 @@ mod tests {
fn invalid_error_type() {
Python::with_gil(|py| {
let err: PyErr = PyErr::new::<crate::types::PyString, _>(());
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
err.restore(py);
let err = PyErr::fetch(py);
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
assert_eq!(
err.to_string(),
"TypeError: exceptions must derive from BaseException"
Expand Down
8 changes: 4 additions & 4 deletions src/exceptions.rs
Expand Up @@ -267,7 +267,7 @@ fn always_throws() -> PyResult<()> {
# Python::with_gil(|py| {
# let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap();
# let err = fun.call0().expect_err(\"called a function that should always return an error but the return value was Ok\");
# assert!(err.is_instance::<Py", $name, ">(py))
# assert!(err.is_instance_of::<Py", $name, ">(py))
# });
```

Expand All @@ -292,7 +292,7 @@ Python::with_gil(|py| {

let error_type = match result {
Ok(_) => \"Not an error\",
Err(error) if error.is_instance::<Py", $name, ">(py) => \"" , $name, "\",
Err(error) if error.is_instance_of::<Py", $name, ">(py) => \"" , $name, "\",
Err(_) => \"Some other error\",
};

Expand Down Expand Up @@ -611,15 +611,15 @@ macro_rules! test_exception {
.unwrap_or($exc_ty::new_err("a test exception"))
};

assert!(err.is_instance::<$exc_ty>(py));
assert!(err.is_instance_of::<$exc_ty>(py));

let value: &$exc_ty = err.instance(py).downcast().unwrap();
assert!(value.source().is_none());

err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause")));
assert!(value.source().is_some());

assert!($crate::PyErr::from(value).is_instance::<$exc_ty>(py));
assert!($crate::PyErr::from(value).is_instance_of::<$exc_ty>(py));
})
}
};
Expand Down
31 changes: 25 additions & 6 deletions src/types/any.rs
Expand Up @@ -74,7 +74,7 @@ impl PyAny {
///
/// Python::with_gil(|py| {
/// let dict = PyDict::new(py);
/// assert!(dict.is_instance::<PyAny>().unwrap());
/// assert!(dict.is_instance_of::<PyAny>().unwrap());
/// let any: &PyAny = dict.as_ref();
/// assert!(any.downcast::<PyDict>().is_ok());
/// assert!(any.downcast::<PyList>().is_err());
Expand Down Expand Up @@ -665,11 +665,21 @@ impl PyAny {
unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) }
}

/// Checks whether this object is an instance of type `typ`.
///
/// This is equivalent to the Python expression `isinstance(self, typ)`.
pub fn is_instance(&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)
}

/// Checks whether this object is an instance of type `T`.
///
/// This is equivalent to the Python expression `isinstance(self, T)`.
pub fn is_instance<T: PyTypeObject>(&self) -> PyResult<bool> {
T::type_object(self.py()).is_instance(self)
/// This is equivalent to the Python expression `isinstance(self, T)`,
/// if the type `T` is known at compile time.
pub fn is_instance_of<T: PyTypeObject>(&self) -> PyResult<bool> {
self.is_instance(T::type_object(self.py()))
}

/// Returns a GIL marker constrained to the lifetime of this type.
Expand All @@ -682,6 +692,7 @@ impl PyAny {
#[cfg(test)]
mod tests {
use crate::{
type_object::PyTypeObject,
types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject,
};
Expand Down Expand Up @@ -776,10 +787,18 @@ mod tests {
fn test_any_isinstance() {
Python::with_gil(|py| {
let x = 5.to_object(py).into_ref(py);
assert!(x.is_instance::<PyLong>().unwrap());
assert!(x.is_instance_of::<PyLong>().unwrap());

let l = vec![x, x].to_object(py).into_ref(py);
assert!(l.is_instance::<PyList>().unwrap());
assert!(l.is_instance_of::<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(PyList::type_object(py)).unwrap());
});
}
}
4 changes: 2 additions & 2 deletions src/types/bytearray.rs
Expand Up @@ -241,7 +241,7 @@ mod tests {
fn test_from_err() {
Python::with_gil(|py| {
if let Err(err) = PyByteArray::from(py, &py.None()) {
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
} else {
panic!("error");
}
Expand Down Expand Up @@ -293,7 +293,7 @@ mod tests {
assert!(py_bytearray_result
.err()
.unwrap()
.is_instance::<PyValueError>(py));
.is_instance_of::<PyValueError>(py));
})
}
}
2 changes: 1 addition & 1 deletion src/types/bytes.rs
Expand Up @@ -174,7 +174,7 @@ mod tests {
assert!(py_bytes_result
.err()
.unwrap()
.is_instance::<PyValueError>(py));
.is_instance_of::<PyValueError>(py));
});
}
}
2 changes: 1 addition & 1 deletion src/types/iterator.rs
Expand Up @@ -206,7 +206,7 @@ mod tests {
let x = 5.to_object(py);
let err = PyIterator::from_object(py, &x).unwrap_err();

assert!(err.is_instance::<PyTypeError>(py));
assert!(err.is_instance_of::<PyTypeError>(py));
});
}

Expand Down
4 changes: 2 additions & 2 deletions src/types/mapping.rs
Expand Up @@ -180,7 +180,7 @@ mod tests {
assert!(mapping
.get_item(8i32)
.unwrap_err()
.is_instance::<PyKeyError>(py));
.is_instance_of::<PyKeyError>(py));
});
}

Expand Down Expand Up @@ -216,7 +216,7 @@ mod tests {
assert!(mapping
.get_item(7i32)
.unwrap_err()
.is_instance::<PyKeyError>(py));
.is_instance_of::<PyKeyError>(py));
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/types/module.rs
Expand Up @@ -146,7 +146,7 @@ impl PyModule {
match self.getattr("__all__") {
Ok(idx) => idx.downcast().map_err(PyErr::from),
Err(err) => {
if err.is_instance::<exceptions::PyAttributeError>(self.py()) {
if err.is_instance_of::<exceptions::PyAttributeError>(self.py()) {
let l = PyList::empty(self.py());
self.setattr("__all__", l).map_err(PyErr::from)?;
Ok(l)
Expand Down
8 changes: 4 additions & 4 deletions src/types/num.rs
Expand Up @@ -345,7 +345,7 @@ mod test_128bit_intergers {
Python::with_gil(|py| {
let obj = py.eval("(1 << 130) * -1", None, None).unwrap();
let err = obj.extract::<i128>().unwrap_err();
assert!(err.is_instance::<crate::exceptions::PyOverflowError>(py));
assert!(err.is_instance_of::<crate::exceptions::PyOverflowError>(py));
})
}

Expand All @@ -354,7 +354,7 @@ mod test_128bit_intergers {
Python::with_gil(|py| {
let obj = py.eval("1 << 130", None, None).unwrap();
let err = obj.extract::<u128>().unwrap_err();
assert!(err.is_instance::<crate::exceptions::PyOverflowError>(py));
assert!(err.is_instance_of::<crate::exceptions::PyOverflowError>(py));
})
}
}
Expand Down Expand Up @@ -421,7 +421,7 @@ mod tests {

let obj = ("123").to_object(py);
let err = obj.extract::<$t>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyTypeError>(py));
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
});
}

Expand All @@ -431,7 +431,7 @@ mod tests {

let obj = (12.3).to_object(py);
let err = obj.extract::<$t>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyTypeError>(py));});
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));});
}

#[test]
Expand Down