Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions include/pybind11/class_support.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}
Expand Down
12 changes: 12 additions & 0 deletions tests/test_methods_and_attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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_<TestPropertiesOverride, TestProperties>(m, "TestPropertiesOverride")
.def(py::init<>())
.def_readonly("def_readonly", &TestPropertiesOverride::value)
.def_readonly_static("def_readonly_static", &TestPropertiesOverride::static_value);

py::class_<SimpleValue>(m, "SimpleValue")
.def_readwrite("value", &SimpleValue::value);

Expand Down
6 changes: 6 additions & 0 deletions tests/test_methods_and_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down