Skip to content

Expose enum_ entries as new "__members__" attribute #666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 3, 2017
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
6 changes: 6 additions & 0 deletions docs/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ typed enums.
>>> int(p.type)
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::

Expand Down
39 changes: 23 additions & 16 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -1116,24 +1116,32 @@ class class_ : public detail::generic_type {
template <typename Type> class enum_ : public class_<Type> {
public:
using class_<Type>::def;
using class_<Type>::def_property_readonly_static;
using Scalar = typename std::underlying_type<Type>::type;
template <typename T> using arithmetic_tag = std::is_same<T, arithmetic>;

template <typename... 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 =
!std::is_same<detail::first_of_t<arithmetic_tag, void, Extra...>,
void>::value;

auto entries = new std::unordered_map<Scalar, const char *>();
def("__repr__", [name, entries](Type value) -> std::string {
auto it = entries->find((Scalar) value);
return std::string(name) + "." +
((it == entries->end()) ? std::string("???")
: std::string(it->second));
auto m_entries_ptr = m_entries.inc_ref().ptr();
def("__repr__", [name, m_entries_ptr](Type value) -> pybind11::str {
for (const auto &kv : reinterpret_borrow<dict>(m_entries_ptr)) {
if (pybind11::cast<Type>(kv.second) == value)
return pybind11::str("{}.{}").format(name, kv.first);
}
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) { new (&value) Type((Type) i); });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this PR but highlighted by the diff, lines 1145 / 1146 look odd.

def("__int__", [](Type value) { return (Scalar) value; });
Expand Down Expand Up @@ -1172,26 +1180,25 @@ template <typename Type> class enum_ : public class_<Type> {
// Pickling and unpickling -- needed for use with the 'multiprocessing' module
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>()); });
m_entries = entries;
}

/// Export enumeration entries into the parent scope
enum_ &export_values() {
for (auto item : reinterpret_borrow<dict>(((PyTypeObject *) this->m_ptr)->tp_dict)) {
if (isinstance(item.second, this->m_ptr))
m_parent.attr(item.first) = item.second;
}
enum_& export_values() {
for (const auto &kv : m_entries)
m_parent.attr(kv.first) = kv.second;
return *this;
}

/// Add an enumeration entry
enum_& value(char const* name, Type value) {
this->attr(name) = pybind11::cast(value, return_value_policy::copy);
(*m_entries)[(Scalar) value] = name;
auto v = pybind11::cast(value, return_value_policy::copy);
this->attr(name) = v;
m_entries[pybind11::str(name)] = v;
return *this;
}

private:
std::unordered_map<Scalar, const char *> *m_entries;
dict m_entries;
handle m_parent;
};

Expand Down
9 changes: 9 additions & 0 deletions tests/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ def test_unscoped_enum():
assert str(UnscopedEnum.EOne) == "UnscopedEnum.EOne"
assert str(UnscopedEnum.ETwo) == "UnscopedEnum.ETwo"
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
y = UnscopedEnum.ETwo
Expand Down