Merge pull request #3638 from PyO3/call-op-bool

Try harder by looking for a __bool__ magic method when extracing bool values from Python objects.
This commit is contained in:
Adam Reichold 2023-12-19 18:52:55 +00:00 committed by GitHub
commit 8bb64377b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 2 deletions

View file

@ -0,0 +1 @@
Values of type `bool` can now be extracted from NumPy's `bool_`.

View file

@ -12,9 +12,15 @@ fn get_type_full_name(obj: &PyAny) -> PyResult<Cow<'_, str>> {
obj.get_type().name()
}
#[pyfunction]
fn accepts_bool(val: bool) -> bool {
val
}
#[pymodule]
pub fn misc(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(issue_219, m)?)?;
m.add_function(wrap_pyfunction!(get_type_full_name, m)?)?;
m.add_function(wrap_pyfunction!(accepts_bool, m)?)?;
Ok(())
}

View file

@ -54,3 +54,13 @@ def test_type_full_name_includes_module():
numpy = pytest.importorskip("numpy")
assert pyo3_pytests.misc.get_type_full_name(numpy.bool_(True)) == "numpy.bool_"
def test_accepts_numpy_bool():
# binary numpy wheel not available on all platforms
numpy = pytest.importorskip("numpy")
assert pyo3_pytests.misc.accepts_bool(True) is True
assert pyo3_pytests.misc.accepts_bool(False) is False
assert pyo3_pytests.misc.accepts_bool(numpy.bool_(True)) is True
assert pyo3_pytests.misc.accepts_bool(numpy.bool_(False)) is False

View file

@ -1,7 +1,8 @@
#[cfg(feature = "experimental-inspect")]
use crate::inspect::types::TypeInfo;
use crate::{
ffi, instance::Py2, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject,
exceptions::PyTypeError, ffi, instance::Py2, FromPyObject, IntoPy, PyAny, PyObject, PyResult,
Python, ToPyObject,
};
/// Represents a Python `bool`.
@ -76,7 +77,52 @@ impl IntoPy<PyObject> for bool {
/// Fails with `TypeError` if the input is not a Python `bool`.
impl<'source> FromPyObject<'source> for bool {
fn extract(obj: &'source PyAny) -> PyResult<Self> {
Ok(obj.downcast::<PyBool>()?.is_true())
let err = match obj.downcast::<PyBool>() {
Ok(obj) => return Ok(obj.is_true()),
Err(err) => err,
};
if obj
.get_type()
.name()
.map_or(false, |name| name == "numpy.bool_")
{
let missing_conversion = |obj: &PyAny| {
PyTypeError::new_err(format!(
"object of type '{}' does not define a '__bool__' conversion",
obj.get_type()
))
};
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
unsafe {
let ptr = obj.as_ptr();
if let Some(tp_as_number) = (*(*ptr).ob_type).tp_as_number.as_ref() {
if let Some(nb_bool) = tp_as_number.nb_bool {
match (nb_bool)(ptr) {
0 => return Ok(false),
1 => return Ok(true),
_ => return Err(crate::PyErr::fetch(obj.py())),
}
}
}
return Err(missing_conversion(obj));
}
#[cfg(any(Py_LIMITED_API, PyPy))]
{
let meth = obj
.lookup_special(crate::intern!(obj.py(), "__bool__"))?
.ok_or_else(|| missing_conversion(obj))?;
let obj = meth.call0()?.downcast::<PyBool>()?;
return Ok(obj.is_true());
}
}
Err(err.into())
}
#[cfg(feature = "experimental-inspect")]