Skip to content

Commit 414ee16

Browse files
authored
Merge pull request #666 from mdcb/master
Expose enum_ entries as new "__members__" attribute
2 parents 11c9f32 + af936e1 commit 414ee16

File tree

3 files changed

+38
-16
lines changed

3 files changed

+38
-16
lines changed

docs/classes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,12 @@ typed enums.
423423
>>> int(p.type)
424424
1L
425425
426+
The entries defined by the enumeration type are exposed in the ``__members__`` property:
427+
428+
.. code-block:: pycon
429+
430+
>>> Pet.Kind.__members__
431+
{'Dog': Kind.Dog, 'Cat': Kind.Cat}
426432
427433
.. note::
428434

include/pybind11/pybind11.h

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,24 +1116,32 @@ class class_ : public detail::generic_type {
11161116
template <typename Type> class enum_ : public class_<Type> {
11171117
public:
11181118
using class_<Type>::def;
1119+
using class_<Type>::def_property_readonly_static;
11191120
using Scalar = typename std::underlying_type<Type>::type;
11201121
template <typename T> using arithmetic_tag = std::is_same<T, arithmetic>;
11211122

11221123
template <typename... Extra>
11231124
enum_(const handle &scope, const char *name, const Extra&... extra)
1124-
: class_<Type>(scope, name, extra...), m_parent(scope) {
1125+
: class_<Type>(scope, name, extra...), m_entries(), m_parent(scope) {
11251126

11261127
constexpr bool is_arithmetic =
11271128
!std::is_same<detail::first_of_t<arithmetic_tag, void, Extra...>,
11281129
void>::value;
11291130

1130-
auto entries = new std::unordered_map<Scalar, const char *>();
1131-
def("__repr__", [name, entries](Type value) -> std::string {
1132-
auto it = entries->find((Scalar) value);
1133-
return std::string(name) + "." +
1134-
((it == entries->end()) ? std::string("???")
1135-
: std::string(it->second));
1131+
auto m_entries_ptr = m_entries.inc_ref().ptr();
1132+
def("__repr__", [name, m_entries_ptr](Type value) -> pybind11::str {
1133+
for (const auto &kv : reinterpret_borrow<dict>(m_entries_ptr)) {
1134+
if (pybind11::cast<Type>(kv.second) == value)
1135+
return pybind11::str("{}.{}").format(name, kv.first);
1136+
}
1137+
return pybind11::str("{}.???").format(name);
11361138
});
1139+
def_property_readonly_static("__members__", [m_entries_ptr](object /* self */) {
1140+
dict m;
1141+
for (const auto &kv : reinterpret_borrow<dict>(m_entries_ptr))
1142+
m[kv.first] = kv.second;
1143+
return m;
1144+
}, return_value_policy::copy);
11371145
def("__init__", [](Type& value, Scalar i) { value = (Type)i; });
11381146
def("__init__", [](Type& value, Scalar i) { new (&value) Type((Type) i); });
11391147
def("__int__", [](Type value) { return (Scalar) value; });
@@ -1172,26 +1180,25 @@ template <typename Type> class enum_ : public class_<Type> {
11721180
// Pickling and unpickling -- needed for use with the 'multiprocessing' module
11731181
def("__getstate__", [](const Type &value) { return pybind11::make_tuple((Scalar) value); });
11741182
def("__setstate__", [](Type &p, tuple t) { new (&p) Type((Type) t[0].cast<Scalar>()); });
1175-
m_entries = entries;
11761183
}
11771184

11781185
/// Export enumeration entries into the parent scope
1179-
enum_ &export_values() {
1180-
for (auto item : reinterpret_borrow<dict>(((PyTypeObject *) this->m_ptr)->tp_dict)) {
1181-
if (isinstance(item.second, this->m_ptr))
1182-
m_parent.attr(item.first) = item.second;
1183-
}
1186+
enum_& export_values() {
1187+
for (const auto &kv : m_entries)
1188+
m_parent.attr(kv.first) = kv.second;
11841189
return *this;
11851190
}
11861191

11871192
/// Add an enumeration entry
11881193
enum_& value(char const* name, Type value) {
1189-
this->attr(name) = pybind11::cast(value, return_value_policy::copy);
1190-
(*m_entries)[(Scalar) value] = name;
1194+
auto v = pybind11::cast(value, return_value_policy::copy);
1195+
this->attr(name) = v;
1196+
m_entries[pybind11::str(name)] = v;
11911197
return *this;
11921198
}
1199+
11931200
private:
1194-
std::unordered_map<Scalar, const char *> *m_entries;
1201+
dict m_entries;
11951202
handle m_parent;
11961203
};
11971204

tests/test_enum.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ def test_unscoped_enum():
77
assert str(UnscopedEnum.EOne) == "UnscopedEnum.EOne"
88
assert str(UnscopedEnum.ETwo) == "UnscopedEnum.ETwo"
99
assert str(EOne) == "UnscopedEnum.EOne"
10+
# __members__ property
11+
assert UnscopedEnum.__members__ == {"EOne": UnscopedEnum.EOne, "ETwo": UnscopedEnum.ETwo}
12+
# __members__ readonly
13+
with pytest.raises(AttributeError):
14+
UnscopedEnum.__members__ = {}
15+
# __members__ returns a copy
16+
foo = UnscopedEnum.__members__
17+
foo["bar"] = "baz"
18+
assert UnscopedEnum.__members__ == {"EOne": UnscopedEnum.EOne, "ETwo": UnscopedEnum.ETwo}
1019

1120
# no TypeError exception for unscoped enum ==/!= int comparisons
1221
y = UnscopedEnum.ETwo

0 commit comments

Comments
 (0)