Merge pull request #505 from PyO3/fix_getattr

Use existing fields and methods before calling custom __getattr__
This commit is contained in:
konstin 2019-07-13 20:39:14 +02:00 committed by GitHub
commit 96a56fa9b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 6 deletions

View file

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
* `module` argument to `pyclass` macro. [#499](https://github.com/PyO3/pyo3/pull/499)
* `py_run!` macro [#512](https://github.com/PyO3/pyo3/pull/512)
* Use existing fields and methods before calling custom __getattr__. [#505](https://github.com/PyO3/pyo3/pull/512)
### Fixed

View file

@ -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>)
}
}

22
tests/test_dunder.rs Normal file → Executable file
View file

@ -462,3 +462,25 @@ fn weakref_dunder_dict_support() {
"import weakref; assert weakref.ref(inst)() is inst; inst.a = 1; assert inst.a == 1"
);
}
#[pyclass]
struct ClassWithGetAttr {
#[pyo3(get, set)]
data: u32,
}
#[pyproto]
impl PyObjectProtocol for ClassWithGetAttr {
fn __getattr__(&self, _name: &str) -> PyResult<u32> {
Ok(self.data * 2)
}
}
#[test]
fn getattr_doesnt_override_member() {
let gil = Python::acquire_gil();
let py = gil.python();
let inst = PyRef::new(py, ClassWithGetAttr { data: 4 }).unwrap();
py_assert!(py, inst, "inst.data == 4");
py_assert!(py, inst, "inst.a == 8");
}