diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b1219cd09e..73b42e3e8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - Restrict FFI definitions `PyGILState_Check` and `Py_tracefunc` to the unlimited API. [#1787](https://github.com/PyO3/pyo3/pull/1787) +- Raise `AttributeError` to avoid panic when calling `del` on a `[setter]` defined class property. [#1775](https://github.com/PyO3/pyo3/issues/1775) ## [0.14.2] - 2021-08-09 diff --git a/guide/src/class.md b/guide/src/class.md index 3b5d3509cfb..27e662a7a26 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -454,6 +454,10 @@ impl MyClass { In this case, the property `number` is defined and available from Python code as `self.number`. +Attributes defined by `#[setter]` or `#[pyo3(set)]` will always raise `AttributeError` on `del` +operations. Support for defining custom `del` behavior is tracked in +[#1778](https://github.com/PyO3/pyo3/issues/1778). + ## Instance methods To define a Python compatible method, an `impl` block for your struct has to be annotated with the diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 82b70fe665a..9af38f021ab 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -218,7 +218,11 @@ pub fn impl_py_setter_def(cls: &syn::Type, property_type: PropertyType) -> Resul ) -> std::os::raw::c_int { pyo3::callback::handle_panic(|_py| { #slf - let _value = _py.from_borrowed_ptr::(_value); + let _value = _py + .from_borrowed_ptr_or_opt(_value) + .ok_or_else(|| { + pyo3::exceptions::PyAttributeError::new_err("can't delete attribute") + })?; let _val = pyo3::FromPyObject::extract(_value)?; pyo3::callback::convert(_py, #setter_impl) diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 53e99803bb7..38b9761ae8e 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -54,6 +54,8 @@ fn class_with_properties() { py_run!(py, inst, "inst.DATA = 20"); py_run!(py, inst, "assert inst.get_num() == 20 == inst.DATA"); + py_expect_exception!(py, inst, "del inst.DATA", PyAttributeError); + py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 20"); py_run!(py, inst, "inst.unwrapped = 42"); py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 42");