diff --git a/include/pybind11/class_support.h b/include/pybind11/class_support.h index 992703ff3..235df45f1 100644 --- a/include/pybind11/class_support.h +++ b/include/pybind11/class_support.h @@ -124,8 +124,15 @@ extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyOb // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`). PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name); - // Call `static_property.__set__()` instead of replacing the `static_property`. - if (descr && PyObject_IsInstance(descr, (PyObject *) get_internals().static_property_type)) { + // The following assignment combinations are possible: + // 1. `Type.static_prop = value` --> descr_set: `Type.static_prop.__set__(value)` + // 2. `Type.static_prop = other_static_prop` --> setattro: replace existing `static_prop` + // 3. `Type.regular_attribute = value` --> setattro: regular attribute assignment + const auto static_prop = (PyObject *) get_internals().static_property_type; + const auto call_descr_set = descr && PyObject_IsInstance(descr, static_prop) + && !PyObject_IsInstance(value, static_prop); + if (call_descr_set) { + // Call `static_property.__set__()` instead of replacing the `static_property`. #if !defined(PYPY_VERSION) return Py_TYPE(descr)->tp_descr_set(descr, obj, value); #else @@ -137,6 +144,7 @@ extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyOb } #endif } else { + // Replace existing attribute. return PyType_Type.tp_setattro(obj, name, value); } } diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp index b7b2edfd8..9b52b9915 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -75,6 +75,13 @@ struct TestProperties { int TestProperties::static_value = 1; +struct TestPropertiesOverride : TestProperties { + int value = 99; + static int static_value; +}; + +int TestPropertiesOverride::static_value = 99; + struct SimpleValue { int value = 1; }; struct TestPropRVP { @@ -219,6 +226,11 @@ test_initializer methods_and_attributes([](py::module &m) { [](py::object cls) { return cls; }, [](py::object cls, py::function f) { f(cls); }); + py::class_(m, "TestPropertiesOverride") + .def(py::init<>()) + .def_readonly("def_readonly", &TestPropertiesOverride::value) + .def_readonly_static("def_readonly_static", &TestPropertiesOverride::static_value); + py::class_(m, "SimpleValue") .def_readwrite("value", &SimpleValue::value); diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index f185ac26d..18bf8d9b3 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -110,6 +110,12 @@ def test_static_properties(): assert Type.def_readwrite_static == 2 assert instance.def_readwrite_static == 2 + # It should be possible to override properties in derived classes + from pybind11_tests import TestPropertiesOverride as TypeOverride + + assert TypeOverride().def_readonly == 99 + assert TypeOverride.def_readonly_static == 99 + def test_static_cls(): """Static property getter and setters expect the type object as the their only argument"""