diff --git a/CHANGELOG.md b/CHANGELOG.md index beb3e35d02d..fe366d975dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added * `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499) + * Use existing fields and methods before calling custom __getattr__. ## [0.7.0] - 2018-05-26 diff --git a/src/class/basic.rs b/src/class/basic.rs index ed71e2d9d73..a23c271801a 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -207,12 +207,37 @@ where T: for<'p> PyObjectGetAttrProtocol<'p>, { fn tp_getattro() -> Option { - py_binary_func!( - PyObjectGetAttrProtocol, - T::__getattr__, - T::Success, - PyObjectCallbackConverter - ) + #[allow(unused_mut)] + unsafe extern "C" fn wrap( + slf: *mut ffi::PyObject, + arg: *mut ffi::PyObject, + ) -> *mut ffi::PyObject + where + T: for<'p> PyObjectGetAttrProtocol<'p>, + { + let _pool = crate::GILPool::new(); + let py = Python::assume_gil_acquired(); + + // Behave like python's __getattr__ (as opposed to __getattribute__) and check + // for existing fields and methods first + let existing = ffi::PyObject_GenericGetAttr(slf, arg); + if existing == std::ptr::null_mut() { + // PyObject_HasAttr also tries to get an object and clears the error if it fails + ffi::PyErr_Clear(); + } else { + return existing; + } + + let slf = py.mut_from_borrowed_ptr::(slf); + let arg = py.from_borrowed_ptr::(arg); + + let result = match arg.extract() { + Ok(arg) => slf.__getattr__(arg).into(), + Err(e) => Err(e.into()), + }; + crate::callback::cb_convert(PyObjectCallbackConverter, py, result) + } + Some(wrap::) } }