Expose enum_ entries as "__members__" read-only property. Getters get a copy.

This commit is contained in:
Matthieu Bec 2017-03-03 08:45:50 -08:00
parent 11c9f32c0f
commit af936e1987
3 changed files with 38 additions and 16 deletions

View File

@ -423,6 +423,12 @@ typed enums.
>>> int(p.type) >>> int(p.type)
1L 1L
The entries defined by the enumeration type are exposed in the ``__members__`` property:
.. code-block:: pycon
>>> Pet.Kind.__members__
{'Dog': Kind.Dog, 'Cat': Kind.Cat}
.. note:: .. note::

View File

@ -1116,24 +1116,32 @@ private:
template <typename Type> class enum_ : public class_<Type> { template <typename Type> class enum_ : public class_<Type> {
public: public:
using class_<Type>::def; using class_<Type>::def;
using class_<Type>::def_property_readonly_static;
using Scalar = typename std::underlying_type<Type>::type; using Scalar = typename std::underlying_type<Type>::type;
template <typename T> using arithmetic_tag = std::is_same<T, arithmetic>; template <typename T> using arithmetic_tag = std::is_same<T, arithmetic>;
template <typename... Extra> template <typename... Extra>
enum_(const handle &scope, const char *name, const Extra&... extra) enum_(const handle &scope, const char *name, const Extra&... extra)
: class_<Type>(scope, name, extra...), m_parent(scope) { : class_<Type>(scope, name, extra...), m_entries(), m_parent(scope) {
constexpr bool is_arithmetic = constexpr bool is_arithmetic =
!std::is_same<detail::first_of_t<arithmetic_tag, void, Extra...>, !std::is_same<detail::first_of_t<arithmetic_tag, void, Extra...>,
void>::value; void>::value;
auto entries = new std::unordered_map<Scalar, const char *>(); auto m_entries_ptr = m_entries.inc_ref().ptr();
def("__repr__", [name, entries](Type value) -> std::string { def("__repr__", [name, m_entries_ptr](Type value) -> pybind11::str {
auto it = entries->find((Scalar) value); for (const auto &kv : reinterpret_borrow<dict>(m_entries_ptr)) {
return std::string(name) + "." + if (pybind11::cast<Type>(kv.second) == value)
((it == entries->end()) ? std::string("???") return pybind11::str("{}.{}").format(name, kv.first);
: std::string(it->second)); }
return pybind11::str("{}.???").format(name);
}); });
def_property_readonly_static("__members__", [m_entries_ptr](object /* self */) {
dict m;
for (const auto &kv : reinterpret_borrow<dict>(m_entries_ptr))
m[kv.first] = kv.second;
return m;
}, return_value_policy::copy);
def("__init__", [](Type& value, Scalar i) { value = (Type)i; }); def("__init__", [](Type& value, Scalar i) { value = (Type)i; });
def("__init__", [](Type& value, Scalar i) { new (&value) Type((Type) i); }); def("__init__", [](Type& value, Scalar i) { new (&value) Type((Type) i); });
def("__int__", [](Type value) { return (Scalar) value; }); def("__int__", [](Type value) { return (Scalar) value; });
@ -1172,26 +1180,25 @@ public:
// Pickling and unpickling -- needed for use with the 'multiprocessing' module // Pickling and unpickling -- needed for use with the 'multiprocessing' module
def("__getstate__", [](const Type &value) { return pybind11::make_tuple((Scalar) value); }); def("__getstate__", [](const Type &value) { return pybind11::make_tuple((Scalar) value); });
def("__setstate__", [](Type &p, tuple t) { new (&p) Type((Type) t[0].cast<Scalar>()); }); def("__setstate__", [](Type &p, tuple t) { new (&p) Type((Type) t[0].cast<Scalar>()); });
m_entries = entries;
} }
/// Export enumeration entries into the parent scope /// Export enumeration entries into the parent scope
enum_& export_values() { enum_& export_values() {
for (auto item : reinterpret_borrow<dict>(((PyTypeObject *) this->m_ptr)->tp_dict)) { for (const auto &kv : m_entries)
if (isinstance(item.second, this->m_ptr)) m_parent.attr(kv.first) = kv.second;
m_parent.attr(item.first) = item.second;
}
return *this; return *this;
} }
/// Add an enumeration entry /// Add an enumeration entry
enum_& value(char const* name, Type value) { enum_& value(char const* name, Type value) {
this->attr(name) = pybind11::cast(value, return_value_policy::copy); auto v = pybind11::cast(value, return_value_policy::copy);
(*m_entries)[(Scalar) value] = name; this->attr(name) = v;
m_entries[pybind11::str(name)] = v;
return *this; return *this;
} }
private: private:
std::unordered_map<Scalar, const char *> *m_entries; dict m_entries;
handle m_parent; handle m_parent;
}; };

View File

@ -7,6 +7,15 @@ def test_unscoped_enum():
assert str(UnscopedEnum.EOne) == "UnscopedEnum.EOne" assert str(UnscopedEnum.EOne) == "UnscopedEnum.EOne"
assert str(UnscopedEnum.ETwo) == "UnscopedEnum.ETwo" assert str(UnscopedEnum.ETwo) == "UnscopedEnum.ETwo"
assert str(EOne) == "UnscopedEnum.EOne" assert str(EOne) == "UnscopedEnum.EOne"
# __members__ property
assert UnscopedEnum.__members__ == {"EOne": UnscopedEnum.EOne, "ETwo": UnscopedEnum.ETwo}
# __members__ readonly
with pytest.raises(AttributeError):
UnscopedEnum.__members__ = {}
# __members__ returns a copy
foo = UnscopedEnum.__members__
foo["bar"] = "baz"
assert UnscopedEnum.__members__ == {"EOne": UnscopedEnum.EOne, "ETwo": UnscopedEnum.ETwo}
# no TypeError exception for unscoped enum ==/!= int comparisons # no TypeError exception for unscoped enum ==/!= int comparisons
y = UnscopedEnum.ETwo y = UnscopedEnum.ETwo