From 2cbed02d957b32e0ffe22815af8abde4d01e9b21 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Tue, 22 Dec 2020 18:56:31 -0600 Subject: [PATCH 1/3] enum: Add Enum.value property --- include/pybind11/pybind11.h | 24 ++++++++++++++++++++++++ tests/test_enum.py | 23 ++++++++++++++++------- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 104f32206d..efc1436aef 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -46,6 +46,7 @@ #include "options.h" #include "detail/class.h" #include "detail/init.h" +#include "detail/typeid.h" #include #include @@ -1569,6 +1570,23 @@ inline str enum_name(handle arg) { return "???"; } +template +struct enum_value { + static Scalar run(handle arg) { + Type value = pybind11::cast(arg); + return static_cast(value); + } +}; + +template +struct enum_value { + static Scalar run(handle) { + throw pybind11::cast_error( + "Enum for " + type_id() + " is not convertible to " + + type_id()); + } +}; + struct enum_base { enum_base(handle base, handle parent) : m_base(base), m_parent(parent) { } @@ -1728,8 +1746,14 @@ template class enum_ : public class_ { : class_(scope, name, extra...), m_base(*this, scope) { constexpr bool is_arithmetic = detail::any_of...>::value; constexpr bool is_convertible = std::is_convertible::value; + auto property = handle((PyObject *) &PyProperty_Type); m_base.init(is_arithmetic, is_convertible); + attr("value") = property( + cpp_function( + &detail::enum_value::run, + pybind11::name("value"), is_method(*this))); + def(init([](Scalar i) { return static_cast(i); }), arg("value")); def("__int__", [](Type value) { return (Scalar) value; }); #if PY_MAJOR_VERSION < 3 diff --git a/tests/test_enum.py b/tests/test_enum.py index f3cce8bce5..e9732fa74f 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -13,15 +13,24 @@ def test_unscoped_enum(): # name property assert m.UnscopedEnum.EOne.name == "EOne" + assert m.UnscopedEnum.EOne.value == 1 assert m.UnscopedEnum.ETwo.name == "ETwo" - assert m.EOne.name == "EOne" - # name readonly + assert m.UnscopedEnum.ETwo.value == 2 + assert m.EOne is m.UnscopedEnum.EOne + # name, value readonly with pytest.raises(AttributeError): m.UnscopedEnum.EOne.name = "" - # name returns a copy - foo = m.UnscopedEnum.EOne.name - foo = "bar" + with pytest.raises(AttributeError): + m.UnscopedEnum.EOne.value = 10 + # name, value returns a copy + # TODO: Neither the name nor value tests actually check against aliasing. + # Use a mutable type that has reference semantics. + nonaliased_name = m.UnscopedEnum.EOne.name + nonaliased_name = "bar" # noqa: F841 assert m.UnscopedEnum.EOne.name == "EOne" + nonaliased_value = m.UnscopedEnum.EOne.value + nonaliased_value = 10 # noqa: F841 + assert m.UnscopedEnum.EOne.value == 1 # __members__ property assert m.UnscopedEnum.__members__ == { @@ -33,8 +42,8 @@ def test_unscoped_enum(): with pytest.raises(AttributeError): m.UnscopedEnum.__members__ = {} # __members__ returns a copy - foo = m.UnscopedEnum.__members__ - foo["bar"] = "baz" + nonaliased_members = m.UnscopedEnum.__members__ + nonaliased_members["bar"] = "baz" assert m.UnscopedEnum.__members__ == { "EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo, From f3f32b6c142cb84b3041de0e9950141a32afecd4 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Tue, 22 Dec 2020 19:27:42 -0600 Subject: [PATCH 2/3] simplify --- include/pybind11/pybind11.h | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index efc1436aef..1fd2642d0e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -46,7 +46,6 @@ #include "options.h" #include "detail/class.h" #include "detail/init.h" -#include "detail/typeid.h" #include #include @@ -1570,23 +1569,6 @@ inline str enum_name(handle arg) { return "???"; } -template -struct enum_value { - static Scalar run(handle arg) { - Type value = pybind11::cast(arg); - return static_cast(value); - } -}; - -template -struct enum_value { - static Scalar run(handle) { - throw pybind11::cast_error( - "Enum for " + type_id() + " is not convertible to " + - type_id()); - } -}; - struct enum_base { enum_base(handle base, handle parent) : m_base(base), m_parent(parent) { } @@ -1737,6 +1719,7 @@ template class enum_ : public class_ { using Base = class_; using Base::def; using Base::attr; + using Base::def_property; using Base::def_property_readonly; using Base::def_property_readonly_static; using Scalar = typename std::underlying_type::type; @@ -1746,15 +1729,10 @@ template class enum_ : public class_ { : class_(scope, name, extra...), m_base(*this, scope) { constexpr bool is_arithmetic = detail::any_of...>::value; constexpr bool is_convertible = std::is_convertible::value; - auto property = handle((PyObject *) &PyProperty_Type); m_base.init(is_arithmetic, is_convertible); - attr("value") = property( - cpp_function( - &detail::enum_value::run, - pybind11::name("value"), is_method(*this))); - def(init([](Scalar i) { return static_cast(i); }), arg("value")); + def_property("value", [](Type value) { return (Scalar) value; }, nullptr); def("__int__", [](Type value) { return (Scalar) value; }); #if PY_MAJOR_VERSION < 3 def("__long__", [](Type value) { return (Scalar) value; }); From c3696b98074aa06ad97ec6dc326bbf0e7f51a3b0 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Wed, 23 Dec 2020 16:31:59 -0600 Subject: [PATCH 3/3] address review --- include/pybind11/pybind11.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 1fd2642d0e..a75424679e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1719,7 +1719,6 @@ template class enum_ : public class_ { using Base = class_; using Base::def; using Base::attr; - using Base::def_property; using Base::def_property_readonly; using Base::def_property_readonly_static; using Scalar = typename std::underlying_type::type; @@ -1732,7 +1731,7 @@ template class enum_ : public class_ { m_base.init(is_arithmetic, is_convertible); def(init([](Scalar i) { return static_cast(i); }), arg("value")); - def_property("value", [](Type value) { return (Scalar) value; }, nullptr); + def_property_readonly("value", [](Type value) { return (Scalar) value; }); def("__int__", [](Type value) { return (Scalar) value; }); #if PY_MAJOR_VERSION < 3 def("__long__", [](Type value) { return (Scalar) value; });