diff --git a/newsfragments/5944.added.md b/newsfragments/5944.added.md new file mode 100644 index 00000000000..1cd74a33698 --- /dev/null +++ b/newsfragments/5944.added.md @@ -0,0 +1 @@ +add `pyo3-ffi::compat::PyObject_HasAttrWithError` diff --git a/newsfragments/5944.changed.md b/newsfragments/5944.changed.md new file mode 100644 index 00000000000..c39e081ff5a --- /dev/null +++ b/newsfragments/5944.changed.md @@ -0,0 +1 @@ +use `PyObject_HasAttrWithError` in `PyAnyMethods::hasattr` diff --git a/pyo3-ffi/src/compat/py_3_13.rs b/pyo3-ffi/src/compat/py_3_13.rs index 0c766b0f86a..7c30ea9843a 100644 --- a/pyo3-ffi/src/compat/py_3_13.rs +++ b/pyo3-ffi/src/compat/py_3_13.rs @@ -202,3 +202,15 @@ compat_function!( -1 // other error } ); + +compat_function!( + originally_defined_for(Py_3_13); + + #[inline] + pub unsafe fn PyObject_HasAttrWithError(obj: *mut crate::PyObject, attr_name: *mut crate::PyObject) -> std::ffi::c_int { + let mut res: *mut crate::PyObject = std::ptr::null_mut(); + let rc = crate::compat::PyObject_GetOptionalAttr(obj, attr_name, &mut res); + crate::Py_XDECREF(res); + rc + } +); diff --git a/src/types/any.rs b/src/types/any.rs index 9ae17d35dbc..877c3fbd1a2 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,8 +1,8 @@ use crate::call::PyCallArgs; use crate::class::basic::CompareOp; use crate::conversion::{FromPyObject, IntoPyObject}; -use crate::err::{PyErr, PyResult}; -use crate::exceptions::{PyAttributeError, PyTypeError}; +use crate::err::{error_on_minusone, PyErr, PyResult}; +use crate::exceptions::PyTypeError; use crate::ffi_ptr_ext::FfiPtrExt; use crate::impl_::pycell::PyStaticClassObject; use crate::instance::Bound; @@ -11,7 +11,7 @@ use crate::py_result_ext::PyResultExt; use crate::type_object::{PyTypeCheck, PyTypeInfo}; use crate::types::PySuper; use crate::types::{PyDict, PyIterator, PyList, PyString, PyType}; -use crate::{err, ffi, Borrowed, BoundObject, IntoPyObjectExt, Py, Python}; +use crate::{err, ffi, Borrowed, BoundObject, IntoPyObjectExt, Py}; #[allow(deprecated)] use crate::{DowncastError, DowncastIntoError}; use std::cell::UnsafeCell; @@ -980,17 +980,23 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { where N: IntoPyObject<'py, Target = PyString>, { - // PyObject_HasAttr suppresses all exceptions, which was the behaviour of `hasattr` in Python 2. - // Use an implementation which suppresses only AttributeError, which is consistent with `hasattr` in Python 3. - fn inner(py: Python<'_>, getattr_result: PyResult>) -> PyResult { - match getattr_result { - Ok(_) => Ok(true), - Err(err) if err.is_instance_of::(py) => Ok(false), - Err(e) => Err(e), - } + fn inner<'py>( + any: &Bound<'py, PyAny>, + attr_name: Borrowed<'_, '_, PyString>, + ) -> PyResult { + let result = + unsafe { ffi::compat::PyObject_HasAttrWithError(any.as_ptr(), attr_name.as_ptr()) }; + error_on_minusone(any.py(), result)?; + Ok(result > 0) } - inner(self.py(), self.getattr(attr_name)) + inner( + self, + attr_name + .into_pyobject(self.py()) + .map_err(Into::into)? + .as_borrowed(), + ) } fn getattr(&self, attr_name: N) -> PyResult>