Skip to content

Commit

Permalink
switch is_instance/is_instance_of, is_subclass/is_subclass_of
Browse files Browse the repository at this point in the history
  • Loading branch information
birkenfeld authored and davidhewitt committed Nov 20, 2021
1 parent a83c31a commit 4389315
Show file tree
Hide file tree
Showing 17 changed files with 89 additions and 92 deletions.
17 changes: 6 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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 All @@ -30,11 +35,6 @@ 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 `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)
Expand All @@ -47,11 +47,6 @@ 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
Expand Down
2 changes: 1 addition & 1 deletion guide/src/conversions/traits.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
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
Original file line number Diff line number Diff line change
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 ob.is_instance_of(pathlib_path)? {
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
26 changes: 13 additions & 13 deletions src/types/any.rs
Original file line number Diff line number Diff line change
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,23 +665,23 @@ impl PyAny {
unsafe { self.py().from_owned_ptr(ffi::PyObject_Dir(self.as_ptr())) }
}

/// Checks whether this object is an instance of type `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> {
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> {
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)`,
/// 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.
#[inline]
pub fn py(&self) -> Python<'_> {
Expand Down Expand Up @@ -787,18 +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_of(PyList::type_object(py)).unwrap());
assert!(l.is_instance(PyList::type_object(py)).unwrap());
});
}
}
4 changes: 2 additions & 2 deletions src/types/bytearray.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 4389315

Please sign in to comment.