From 6db9587c2810cc33e0ba37be815be212d212b65a Mon Sep 17 00:00:00 2001 From: konstin <konstin@mailbox.org> Date: Wed, 5 Jun 2019 11:32:46 +0200 Subject: [PATCH] Use existing fields and methods before calling custom __getattr__ Previously, defining `__getattr__` would override all existing fields and methods. This changes it to behave like a `__getattr__` method defined in python, i.e. the custom method is only called if there isn't a field or method of that name --- CHANGELOG.md | 1 + src/class/basic.rs | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) 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<ffi::binaryfunc> { - py_binary_func!( - PyObjectGetAttrProtocol, - T::__getattr__, - T::Success, - PyObjectCallbackConverter - ) + #[allow(unused_mut)] + unsafe extern "C" fn wrap<T>( + 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::<T>(slf); + let arg = py.from_borrowed_ptr::<crate::types::PyAny>(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::<T>) } }