Added write only property functions for issue #1142 (#1144)
py::class_<T>'s `def_property` and `def_property_static` can now take a
`nullptr` as the getter to allow a write-only property to be established
(mirroring Python's `property()` built-in when `None` is given for the
getter).
This also updates properties to use the new nullptr constructor internally.
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 8b7047d..b0d958d 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -15,6 +15,9 @@
for non-MSVC compilers).
`#934 <https://github.com/pybind/pybind11/pull/934>`_.
+* Added support for write only properties.
+ `#1144 <https://github.com/pybind/pybind11/pull/1144>`_.
+
v2.2.1 (September 14, 2017)
-----------------------------------------------------
diff --git a/docs/classes.rst b/docs/classes.rst
index ca2477e..890257d 100644
--- a/docs/classes.rst
+++ b/docs/classes.rst
@@ -155,6 +155,9 @@
.def_property("name", &Pet::getName, &Pet::setName)
// ... remainder ...
+Write only properties can be defined by passing ``nullptr`` as the
+input for the read function.
+
.. seealso::
Similar functions :func:`class_::def_readwrite_static`,
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 30c6917..b489bb2 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -51,6 +51,7 @@
class cpp_function : public function {
public:
cpp_function() { }
+ cpp_function(std::nullptr_t) { }
/// Construct a cpp_function from a vanilla function pointer
template <typename Return, typename... Args, typename... Extra>
@@ -951,18 +952,18 @@
tinfo->get_buffer_data = get_buffer_data;
}
+ // rec_func must be set for either fget or fset.
void def_property_static_impl(const char *name,
handle fget, handle fset,
- detail::function_record *rec_fget) {
- const auto is_static = !(rec_fget->is_method && rec_fget->scope);
- const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
-
+ detail::function_record *rec_func) {
+ const auto is_static = rec_func && !(rec_func->is_method && rec_func->scope);
+ const auto has_doc = rec_func && rec_func->doc && pybind11::options::show_user_defined_docstrings();
auto property = handle((PyObject *) (is_static ? get_internals().static_property_type
: &PyProperty_Type));
attr(name) = property(fget.ptr() ? fget : none(),
fset.ptr() ? fset : none(),
/*deleter*/none(),
- pybind11::str(has_doc ? rec_fget->doc : ""));
+ pybind11::str(has_doc ? rec_func->doc : ""));
}
};
@@ -1196,7 +1197,7 @@
/// Uses cpp_function's return_value_policy by default
template <typename... Extra>
class_ &def_property_readonly(const char *name, const cpp_function &fget, const Extra& ...extra) {
- return def_property(name, fget, cpp_function(), extra...);
+ return def_property(name, fget, nullptr, extra...);
}
/// Uses return_value_policy::reference by default
@@ -1208,7 +1209,7 @@
/// Uses cpp_function's return_value_policy by default
template <typename... Extra>
class_ &def_property_readonly_static(const char *name, const cpp_function &fget, const Extra& ...extra) {
- return def_property_static(name, fget, cpp_function(), extra...);
+ return def_property_static(name, fget, nullptr, extra...);
}
/// Uses return_value_policy::reference_internal by default
@@ -1238,21 +1239,25 @@
template <typename... Extra>
class_ &def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) {
auto rec_fget = get_function_record(fget), rec_fset = get_function_record(fset);
- char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific documentation string */
- detail::process_attributes<Extra...>::init(extra..., rec_fget);
- if (rec_fget->doc && rec_fget->doc != doc_prev) {
- free(doc_prev);
- rec_fget->doc = strdup(rec_fget->doc);
+ auto *rec_active = rec_fget;
+ if (rec_fget) {
+ char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific documentation string */
+ detail::process_attributes<Extra...>::init(extra..., rec_fget);
+ if (rec_fget->doc && rec_fget->doc != doc_prev) {
+ free(doc_prev);
+ rec_fget->doc = strdup(rec_fget->doc);
+ }
}
if (rec_fset) {
- doc_prev = rec_fset->doc;
+ char *doc_prev = rec_fset->doc;
detail::process_attributes<Extra...>::init(extra..., rec_fset);
if (rec_fset->doc && rec_fset->doc != doc_prev) {
free(doc_prev);
rec_fset->doc = strdup(rec_fset->doc);
}
+ if (! rec_active) rec_active = rec_fset;
}
- def_property_static_impl(name, fget, fset, rec_fget);
+ def_property_static_impl(name, fget, fset, rec_active);
return *this;
}
diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp
index cd15869..fde152b 100644
--- a/tests/test_methods_and_attributes.cpp
+++ b/tests/test_methods_and_attributes.cpp
@@ -279,12 +279,20 @@
.def(py::init<>())
.def_readonly("def_readonly", &TestProperties::value)
.def_readwrite("def_readwrite", &TestProperties::value)
+ .def_property("def_writeonly", nullptr,
+ [](TestProperties& s,int v) { s.value = v; } )
+ .def_property("def_property_writeonly", nullptr, &TestProperties::set)
.def_property_readonly("def_property_readonly", &TestProperties::get)
.def_property("def_property", &TestProperties::get, &TestProperties::set)
+ .def_property("def_property_impossible", nullptr, nullptr)
.def_readonly_static("def_readonly_static", &TestProperties::static_value)
.def_readwrite_static("def_readwrite_static", &TestProperties::static_value)
+ .def_property_static("def_writeonly_static", nullptr,
+ [](py::object, int v) { TestProperties::static_value = v; })
.def_property_readonly_static("def_property_readonly_static",
[](py::object) { return TestProperties::static_get(); })
+ .def_property_static("def_property_writeonly_static", nullptr,
+ [](py::object, int v) { return TestProperties::static_set(v); })
.def_property_static("def_property_static",
[](py::object) { return TestProperties::static_get(); },
[](py::object, int v) { TestProperties::static_set(v); })
diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py
index 9fd9cb7..86b2c3b 100644
--- a/tests/test_methods_and_attributes.py
+++ b/tests/test_methods_and_attributes.py
@@ -98,6 +98,21 @@
instance.def_property = 3
assert instance.def_property == 3
+ with pytest.raises(AttributeError) as excinfo:
+ dummy = instance.def_property_writeonly # noqa: F841 unused var
+ assert "unreadable attribute" in str(excinfo)
+
+ instance.def_property_writeonly = 4
+ assert instance.def_property_readonly == 4
+
+ with pytest.raises(AttributeError) as excinfo:
+ dummy = instance.def_property_impossible # noqa: F841 unused var
+ assert "unreadable attribute" in str(excinfo)
+
+ with pytest.raises(AttributeError) as excinfo:
+ instance.def_property_impossible = 5
+ assert "can't set attribute" in str(excinfo)
+
def test_static_properties():
assert m.TestProperties.def_readonly_static == 1
@@ -108,13 +123,27 @@
m.TestProperties.def_readwrite_static = 2
assert m.TestProperties.def_readwrite_static == 2
- assert m.TestProperties.def_property_readonly_static == 2
with pytest.raises(AttributeError) as excinfo:
- m.TestProperties.def_property_readonly_static = 3
+ dummy = m.TestProperties.def_writeonly_static # noqa: F841 unused var
+ assert "unreadable attribute" in str(excinfo)
+
+ m.TestProperties.def_writeonly_static = 3
+ assert m.TestProperties.def_readonly_static == 3
+
+ assert m.TestProperties.def_property_readonly_static == 3
+ with pytest.raises(AttributeError) as excinfo:
+ m.TestProperties.def_property_readonly_static = 99
assert "can't set attribute" in str(excinfo)
- m.TestProperties.def_property_static = 3
- assert m.TestProperties.def_property_static == 3
+ m.TestProperties.def_property_static = 4
+ assert m.TestProperties.def_property_static == 4
+
+ with pytest.raises(AttributeError) as excinfo:
+ dummy = m.TestProperties.def_property_writeonly_static
+ assert "unreadable attribute" in str(excinfo)
+
+ m.TestProperties.def_property_writeonly_static = 5
+ assert m.TestProperties.def_property_static == 5
# Static property read and write via instance
instance = m.TestProperties()
@@ -127,6 +156,13 @@
assert m.TestProperties.def_readwrite_static == 2
assert instance.def_readwrite_static == 2
+ with pytest.raises(AttributeError) as excinfo:
+ dummy = instance.def_property_writeonly_static # noqa: F841 unused var
+ assert "unreadable attribute" in str(excinfo)
+
+ instance.def_property_writeonly_static = 4
+ assert instance.def_property_static == 4
+
# It should be possible to override properties in derived classes
assert m.TestPropertiesOverride().def_readonly == 99
assert m.TestPropertiesOverride.def_readonly_static == 99