mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
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.
This commit is contained in:
parent
71178922fd
commit
0a0758ce3a
@ -15,6 +15,9 @@ v2.3.0 (Not yet released)
|
||||
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)
|
||||
-----------------------------------------------------
|
||||
|
||||
|
@ -155,6 +155,9 @@ the setter and getter functions:
|
||||
.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`,
|
||||
|
@ -51,6 +51,7 @@ NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
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 @@ protected:
|
||||
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 @@ public:
|
||||
/// 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 @@ public:
|
||||
/// 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 @@ public:
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -279,12 +279,20 @@ TEST_SUBMODULE(methods_and_attributes, m) {
|
||||
.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); })
|
||||
|
@ -98,6 +98,21 @@ def test_properties():
|
||||
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 @@ def test_static_properties():
|
||||
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 @@ def test_static_properties():
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user