Skip to content

Add enum value to enum repr #2126

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
Sep 19, 2020
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
34 changes: 18 additions & 16 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,16 @@ detail::initimpl::pickle_factory<GetState, SetState> pickle(GetState &&g, SetSta
}

PYBIND11_NAMESPACE_BEGIN(detail)

inline str enum_name(handle arg) {
dict entries = arg.get_type().attr("__entries");
for (const auto &kv : entries) {
if (handle(kv.second[int_(0)]).equal(arg))
return pybind11::str(kv.first);
}
return "???";
}

struct enum_base {
enum_base(handle base, handle parent) : m_base(base), m_parent(parent) { }

Expand All @@ -1495,29 +1505,21 @@ struct enum_base {
auto static_property = handle((PyObject *) get_internals().static_property_type);

m_base.attr("__repr__") = cpp_function(
[](handle arg) -> str {
[](object arg) -> str {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why handle to object? You don't need reference counting here, do you?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Argh, good catch. I should've look more closely at the details rather than eyeballing the structural changes and shrugging "OK"...

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK, so I just tried this out, and the reason seems to be because there's no int_ conversion from handle (you can reinterpret_steal/reinterpret_borrow, but those will not do the int(...) conversion).

Copy link
Collaborator

Choose a reason for hiding this comment

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

See e.g. __getstate__ and __hash__, also doing that. I don't immediately see a better solution, then? (though if feels there ought to be one, changing the interface of the Python types; but that's a different discussion/PR)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe pass the object by reference, to avoid reference counting?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@bstaletic Hmmm, interesting.

Thinking about it a bit futher: will it, though? I believe (though the cast, make_caster, cast_op, PYBIND11_TYPE_CASTER, ... code is quite convoluted) that the object will be created and moved from the caster anyway, if you take it by value. So it shouldn't matter?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I vote to keep it this way, merge this PR, and create an issue/discussion on why you can't call the int_ conversion on a handle (like you can for str).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm okay with that.

handle type = type::handle_of(arg);
object type_name = type.attr("__name__");
dict entries = type.attr("__entries");
for (auto kv : entries) {
object other = kv.second[int_(0)];
if (other.equal(arg))
return pybind11::str("{}.{}").format(type_name, kv.first);
}
return pybind11::str("{}.???").format(type_name);
return pybind11::str("<{}.{}: {}>").format(type_name, enum_name(arg), int_(arg));
}, name("__repr__"), is_method(m_base)
);

m_base.attr("name") = property(cpp_function(
m_base.attr("name") = property(cpp_function(&enum_name, name("name"), is_method(m_base)));

m_base.attr("__str__") = cpp_function(
[](handle arg) -> str {
dict entries = type::handle_of(arg).attr("__entries");
for (auto kv : entries) {
if (handle(kv.second[int_(0)]).equal(arg))
return pybind11::str(kv.first);
}
return "???";
object type_name = type::handle_of(arg).attr("__name__");
return pybind11::str("{}.{}").format(type_name, enum_name(arg));
}, name("name"), is_method(m_base)
));
);

m_base.attr("__doc__") = static_property(cpp_function(
[](handle arg) -> std::string {
Expand Down
7 changes: 6 additions & 1 deletion tests/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ def test_unscoped_enum():
assert str(m.UnscopedEnum.EOne) == "UnscopedEnum.EOne"
assert str(m.UnscopedEnum.ETwo) == "UnscopedEnum.ETwo"
assert str(m.EOne) == "UnscopedEnum.EOne"
assert repr(m.UnscopedEnum.EOne) == "<UnscopedEnum.EOne: 1>"
assert repr(m.UnscopedEnum.ETwo) == "<UnscopedEnum.ETwo: 2>"
assert repr(m.EOne) == "<UnscopedEnum.EOne: 1>"

# name property
assert m.UnscopedEnum.EOne.name == "EOne"
Expand Down Expand Up @@ -143,6 +146,8 @@ def test_scoped_enum():
def test_implicit_conversion():
assert str(m.ClassWithUnscopedEnum.EMode.EFirstMode) == "EMode.EFirstMode"
assert str(m.ClassWithUnscopedEnum.EFirstMode) == "EMode.EFirstMode"
assert repr(m.ClassWithUnscopedEnum.EMode.EFirstMode) == "<EMode.EFirstMode: 1>"
assert repr(m.ClassWithUnscopedEnum.EFirstMode) == "<EMode.EFirstMode: 1>"

f = m.ClassWithUnscopedEnum.test_function
first = m.ClassWithUnscopedEnum.EFirstMode
Expand All @@ -167,7 +172,7 @@ def test_implicit_conversion():
x[f(first)] = 3
x[f(second)] = 4
# Hashing test
assert str(x) == "{EMode.EFirstMode: 3, EMode.ESecondMode: 4}"
assert repr(x) == "{<EMode.EFirstMode: 1>: 3, <EMode.ESecondMode: 2>: 4}"


def test_binary_operators():
Expand Down