From 4ef8fa0eebc9975dcadf7e1286553ede4b5344bc Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 5 Apr 2017 17:31:06 +0100 Subject: [PATCH 01/11] Implement py3_enum<> mapping to enum.IntEnum --- include/pybind11/cast.h | 71 +++++++++++++++++++++++++++++++++++++ include/pybind11/pybind11.h | 33 +++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 6ca49a89eb..4726230ffe 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -950,6 +950,77 @@ template class type_caster> { std::tuple...> value; }; +struct py3_enum_info { + handle type = {}; + std::unordered_map values = {}; + + py3_enum_info() = default; + + py3_enum_info(handle type, const dict& values) : type(type) { + for (auto item : values) + this->values[static_cast(item.second.cast())] = type.attr(item.first); + } + + static std::unordered_map& registry() { + static std::unordered_map map = {}; + return map; + } + + template + static void bind(handle type, const dict& values) { + registry()[typeid(T)] = py3_enum_info(type, values); + } + + template + static const py3_enum_info* get() { + auto it = registry().find(typeid(T)); + return it == registry().end() ? nullptr : &it->second; + } +}; + +template +struct type_caster::value>> { +private: + using base_caster = type_caster_base; + base_caster caster; + bool py3 = false; + T value; + +public: + template using cast_op_type = pybind11::detail::cast_op_type; + + operator T*() { return py3 ? &value : static_cast(caster); } + operator T&() { return py3 ? value : static_cast(caster); } + + static handle cast(const T& src, return_value_policy rvp, handle parent) { + if (auto info = py3_enum_info::get()) { + auto it = info->values.find(static_cast(src)); + if (it == info->values.end()) + return {}; + return it->second.inc_ref(); + } + return base_caster::cast(src, rvp, parent); + } + + bool load(handle src, bool convert) { + if (!src) + return false; + if (auto info = py3_enum_info::get()) { + py3 = true; + if (!isinstance(src, info->type)) + return false; + value = static_cast(src.cast()); + return true; + } + py3 = false; + return caster.load(src, convert); + } + + static PYBIND11_DESCR name() { + return base_caster::name(); + } +}; + /// Helper class which abstracts away certain actions. Users can provide specializations for /// custom holders, but it's only necessary if the type has a non-standard interface. template diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 62ee786243..c2c2233b0f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1257,6 +1257,39 @@ template class enum_ : public class_ { handle m_parent; }; +template +class py3_enum { +public: + using underlying_type = typename std::underlying_type::type; + + py3_enum(handle scope, const char* name) + : name(name), + parent(scope), + ctor(module::import("enum").attr("IntEnum")), + unique(module::import("enum").attr("unique")) { + update(); + } + + py3_enum& value(const char* name, T value) { + entries[name] = cast(static_cast(value)); + update(); + return *this; + } + +private: + const char *name; + handle parent; + dict entries; + object ctor; + object unique; + + void update() { + object type = unique(ctor(name, entries)); + setattr(parent, name, type); + detail::py3_enum_info::bind(type, entries); + } +}; + NAMESPACE_BEGIN(detail) template struct init { template = 0> From 53745641277f6ca5a6d206c46e778404abfcf7da Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 5 Apr 2017 17:36:08 +0100 Subject: [PATCH 02/11] Add tests for py3 enums --- tests/conftest.py | 4 +++- tests/test_enum.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_enum.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5b08004e3f..ca00837e98 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -200,6 +200,7 @@ def pytest_namespace(): except ImportError: have_eigen = False pypy = platform.python_implementation() == "PyPy" + py3 = sys.version_info.major == 3 skipif = pytest.mark.skipif return { @@ -211,7 +212,8 @@ def pytest_namespace(): 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, reason="eigen and/or scipy are not installed"), 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), - 'gc_collect': gc_collect + 'gc_collect': gc_collect, + 'requires_py3': skipif(not py3, reason='requires Python 3') } diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 67341f4b08..aa555b161a 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -37,6 +37,23 @@ class ClassWithUnscopedEnum { } }; +enum Py3Enum { + A = -42, + B = 1, + C = 42, +}; + +enum class Py3EnumScoped : short { + X = 10, + Y = -1024, +}; + +enum class Py3EnumEmpty {}; + +enum class Py3EnumNonUnique { + X = 1 +}; + std::string test_scoped_enum(ScopedEnum z) { return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three"); } @@ -59,6 +76,33 @@ test_initializer enums([](py::module &m) { .value("Execute", Flags::Execute) .export_values(); +#if PY_VERSION_HEX >= 0x03000000 + py::py3_enum(m, "Py3EnumEmpty"); + + py::py3_enum(m, "Py3Enum") + .value("A", Py3Enum::A) + .value("B", Py3Enum::B) + .value("C", Py3Enum::C); + + py::py3_enum(m, "Py3EnumScoped") + .value("X", Py3EnumScoped::X) + .value("Y", Py3EnumScoped::Y); + + m.def("make_py3_enum", [](bool x) { + return x ? Py3EnumScoped::X : Py3EnumScoped::Y; + }); + + m.def("take_py3_enum", [](Py3EnumScoped x) { + return x == Py3EnumScoped::X; + }); + + m.def("non_unique_py3_enum", [=]() { + py::py3_enum(m, "Py3EnumNonUnique") + .value("X", Py3EnumNonUnique::X) + .value("Y", Py3EnumNonUnique::X); + }); +#endif + py::class_ exenum_class(m, "ClassWithUnscopedEnum"); exenum_class.def_static("test_function", &ClassWithUnscopedEnum::test_function); py::enum_(exenum_class, "EMode") diff --git a/tests/test_enum.py b/tests/test_enum.py index 6cc4887c5c..7d730e5168 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -127,3 +127,33 @@ def test_enum_to_int(): test_enum_to_uint(ClassWithUnscopedEnum.EMode.EFirstMode) test_enum_to_long_long(Flags.Read) test_enum_to_long_long(ClassWithUnscopedEnum.EMode.EFirstMode) + + +@pytest.requires_py3 +def test_py3_enum(): + from pybind11_tests import ( + Py3Enum, Py3EnumEmpty, Py3EnumScoped, + make_py3_enum, take_py3_enum, non_unique_py3_enum + ) + + from enum import IntEnum + + expected = { + Py3Enum: [('A', -42), ('B', 1), ('C', 42)], + Py3EnumEmpty: [], + Py3EnumScoped: [('X', 10), ('Y', -1024)] + } + + for tp, entries in expected.items(): + assert issubclass(tp, IntEnum) + assert sorted(tp.__members__.items()) == entries + + assert make_py3_enum(True) is Py3EnumScoped.X + assert make_py3_enum(False) is Py3EnumScoped.Y + + assert take_py3_enum(Py3EnumScoped.X) + assert not take_py3_enum(Py3EnumScoped.Y) + + with pytest.raises(ValueError) as excinfo: + non_unique_py3_enum() + assert 'duplicate values found' in str(excinfo.value) From 39c8b4cc66d551840c516b60b8f877859d181644 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 5 Apr 2017 23:47:08 +0100 Subject: [PATCH 03/11] Add docs on py3 enums --- docs/classes.rst | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/classes.rst b/docs/classes.rst index 30fb2a2d5f..4d2b47bd12 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -443,3 +443,46 @@ The entries defined by the enumeration type are exposed in the ``__members__`` p ... By default, these are omitted to conserve space. + +Python 3 enumerations +===================== + +Python 3 provides support for rich enumeration types in ``enum`` module [#enum]_ of the +standard library; it can be also used in Python 2 by installing ``enum34`` backport package. + +In order to expose a C++ enumeration type as a subclass of ``enum.IntEnum`` in Python, +one can use the :class:`py3_enum` wrapper whose interface is similar to that of :class:`enum_`: + +.. code-block:: cpp + + enum class Captain { + Kirk = 0, + Picard + }; + +The binding code will look like this: + +.. code-block:: cpp + + py::py3_enum(m, "Captain") + .value("Kirk", Captain::Kirk) + .value("Picard", Captain::Picard); + +The corresponding Python type is a subclass of ``enum.IntEnum`` and, as a result, is also +a subclass of ``int``: + +.. code-block:: pycon + + >>> Captain.mro() + [, , int, , object] + >>> list(Captain) + [, ] + >>> int(Captain.Picard) + 1 + >>> Captain.Kirk.name, Captain.Kirk.value + ('Kirk', 0) + +For more details on ``IntEnum`` functionality, see the official docs [#intenum]_. + +.. [#enum] https://docs.python.org/3/library/enum.html +.. [#intenum] https://docs.python.org/3/library/enum.html#intenum From 0ec6b94baef91f98e9b20e012e9a527d1145dbd4 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Thu, 6 Apr 2017 14:37:53 +0100 Subject: [PATCH 04/11] Move most of py3 enum impl into the type caster --- include/pybind11/cast.h | 55 +++++++++++++++++-------------------- include/pybind11/pybind11.h | 6 ++-- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4726230ffe..5e5d00ce4e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -950,52 +950,45 @@ template class type_caster> { std::tuple...> value; }; +template struct py3_enum_info { handle type = {}; - std::unordered_map values = {}; + std::unordered_map values = {}; py3_enum_info() = default; py3_enum_info(handle type, const dict& values) : type(type) { - for (auto item : values) - this->values[static_cast(item.second.cast())] = type.attr(item.first); - } - - static std::unordered_map& registry() { - static std::unordered_map map = {}; - return map; - } - - template - static void bind(handle type, const dict& values) { - registry()[typeid(T)] = py3_enum_info(type, values); - } - - template - static const py3_enum_info* get() { - auto it = registry().find(typeid(T)); - return it == registry().end() ? nullptr : &it->second; + for (auto item : values) { + this->values[item.second.cast()] = type.attr(item.first); + } } }; template struct type_caster::value>> { + using underlying_type = typename std::underlying_type::type; + private: using base_caster = type_caster_base; + + static std::unique_ptr>& py3_info() { + static std::unique_ptr> info; + return info; + } + base_caster caster; - bool py3 = false; T value; public: template using cast_op_type = pybind11::detail::cast_op_type; - operator T*() { return py3 ? &value : static_cast(caster); } - operator T&() { return py3 ? value : static_cast(caster); } + operator T*() { return py3_info() ? &value : static_cast(caster); } + operator T&() { return py3_info() ? value : static_cast(caster); } static handle cast(const T& src, return_value_policy rvp, handle parent) { - if (auto info = py3_enum_info::get()) { - auto it = info->values.find(static_cast(src)); - if (it == info->values.end()) + if (py3_info()) { + auto it = py3_info()->values.find(static_cast(src)); + if (it == py3_info()->values.end()) return {}; return it->second.inc_ref(); } @@ -1005,20 +998,22 @@ struct type_caster::value>> { bool load(handle src, bool convert) { if (!src) return false; - if (auto info = py3_enum_info::get()) { - py3 = true; - if (!isinstance(src, info->type)) + if (py3_info()) { + if (!isinstance(src, py3_info()->type)) return false; - value = static_cast(src.cast()); + value = static_cast(src.cast()); return true; } - py3 = false; return caster.load(src, convert); } static PYBIND11_DESCR name() { return base_caster::name(); } + + static void bind(handle type, const dict& values) { + py3_info().reset(new py3_enum_info(type, values)); + } }; /// Helper class which abstracts away certain actions. Users can provide specializations for diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index c2c2233b0f..9f434dca03 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1262,8 +1262,8 @@ class py3_enum { public: using underlying_type = typename std::underlying_type::type; - py3_enum(handle scope, const char* name) - : name(name), + py3_enum(handle scope, const char* enum_name) + : name(enum_name), parent(scope), ctor(module::import("enum").attr("IntEnum")), unique(module::import("enum").attr("unique")) { @@ -1286,7 +1286,7 @@ class py3_enum { void update() { object type = unique(ctor(name, entries)); setattr(parent, name, type); - detail::py3_enum_info::bind(type, entries); + detail::type_caster::bind(type, entries); } }; From dd30f473bc68ab91984f7ff424aa2f0765973ee0 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Thu, 6 Apr 2017 23:23:09 +0100 Subject: [PATCH 05/11] Assign __module__ properly for py3 enums --- include/pybind11/pybind11.h | 21 +++++++++++++++------ tests/test_enum.py | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 9f434dca03..d55a463d4b 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1262,11 +1262,19 @@ class py3_enum { public: using underlying_type = typename std::underlying_type::type; - py3_enum(handle scope, const char* enum_name) - : name(enum_name), - parent(scope), + py3_enum(handle scope, const char* name) + : name(name), + scope(scope), ctor(module::import("enum").attr("IntEnum")), unique(module::import("enum").attr("unique")) { + kwargs["value"] = cast(name); + kwargs["names"] = entries; + if (scope) { + if (hasattr(scope, "__module__")) + kwargs["module"] = scope.attr("__module__"); + else if (hasattr(scope, "__name__")) + kwargs["module"] = scope.attr("__name__"); + } update(); } @@ -1278,14 +1286,15 @@ class py3_enum { private: const char *name; - handle parent; + handle scope; dict entries; object ctor; object unique; + dict kwargs; void update() { - object type = unique(ctor(name, entries)); - setattr(parent, name, type); + object type = unique(ctor(**kwargs)); + setattr(scope, name, type); detail::type_caster::bind(type, entries); } }; diff --git a/tests/test_enum.py b/tests/test_enum.py index 7d730e5168..66da1124c8 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -147,6 +147,7 @@ def test_py3_enum(): for tp, entries in expected.items(): assert issubclass(tp, IntEnum) assert sorted(tp.__members__.items()) == entries + assert tp.__module__ == 'pybind11_tests' assert make_py3_enum(True) is Py3EnumScoped.X assert make_py3_enum(False) is Py3EnumScoped.Y From d4ef280cb9caa68bf4f4cc3dc9a5186455ca8d65 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Thu, 6 Apr 2017 23:31:02 +0100 Subject: [PATCH 06/11] Set __qualname__ for py3 enums (PY >= 3.3) --- include/pybind11/pybind11.h | 4 ++++ tests/test_enum.cpp | 5 ++++- tests/test_enum.py | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index d55a463d4b..ae029dd431 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1274,6 +1274,10 @@ class py3_enum { kwargs["module"] = scope.attr("__module__"); else if (hasattr(scope, "__name__")) kwargs["module"] = scope.attr("__name__"); +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 + if (hasattr(scope, "__qualname__")) + kwargs["qualname"] = scope.attr("__qualname__").cast() + "." + name; +#endif } update(); } diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index aa555b161a..cebaa4ad8b 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -54,6 +54,8 @@ enum class Py3EnumNonUnique { X = 1 }; +class DummyScope {}; + std::string test_scoped_enum(ScopedEnum z) { return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three"); } @@ -77,7 +79,8 @@ test_initializer enums([](py::module &m) { .export_values(); #if PY_VERSION_HEX >= 0x03000000 - py::py3_enum(m, "Py3EnumEmpty"); + auto scope = py::class_(m, "DummyScope"); + py::py3_enum(scope, "Py3EnumEmpty"); py::py3_enum(m, "Py3Enum") .value("A", Py3Enum::A) diff --git a/tests/test_enum.py b/tests/test_enum.py index 66da1124c8..749de49e2d 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -132,10 +132,12 @@ def test_enum_to_int(): @pytest.requires_py3 def test_py3_enum(): from pybind11_tests import ( - Py3Enum, Py3EnumEmpty, Py3EnumScoped, + Py3Enum, DummyScope, Py3EnumScoped, make_py3_enum, take_py3_enum, non_unique_py3_enum ) + Py3EnumEmpty = DummyScope.Py3EnumEmpty + from enum import IntEnum expected = { @@ -149,6 +151,10 @@ def test_py3_enum(): assert sorted(tp.__members__.items()) == entries assert tp.__module__ == 'pybind11_tests' + assert Py3Enum.__qualname__ == 'Py3Enum' + assert Py3EnumEmpty.__qualname__ == 'DummyScope.Py3EnumEmpty' + assert Py3EnumScoped.__qualname__ == 'Py3EnumScoped' + assert make_py3_enum(True) is Py3EnumScoped.X assert make_py3_enum(False) is Py3EnumScoped.Y From 65487c012db91d963222a71372607130d1084f67 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Fri, 7 Apr 2017 00:06:43 +0100 Subject: [PATCH 07/11] Allow extending py3 enums --- include/pybind11/pybind11.h | 22 ++++++++++++++++++---- tests/test_enum.cpp | 8 ++++++-- tests/test_enum.py | 4 ++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index ae029dd431..d21ad2ac13 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1282,12 +1282,20 @@ class py3_enum { update(); } - py3_enum& value(const char* name, T value) { - entries[name] = cast(static_cast(value)); - update(); + py3_enum& value(const char* name, T value) & { + add_entry(name, value); return *this; } + py3_enum&& value(const char* name, T value) && { + add_entry(name, value); + return std::move(*this); + } + + class_ extend() && { + return cast>(type); + } + private: const char *name; handle scope; @@ -1295,9 +1303,15 @@ class py3_enum { object ctor; object unique; dict kwargs; + object type; + + void add_entry(const char *name, T value) { + entries[name] = cast(static_cast(value)); + update(); + } void update() { - object type = unique(ctor(**kwargs)); + type = unique(ctor(**kwargs)); setattr(scope, name, type); detail::type_caster::bind(type, entries); } diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index cebaa4ad8b..f157f61640 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -82,10 +82,14 @@ test_initializer enums([](py::module &m) { auto scope = py::class_(m, "DummyScope"); py::py3_enum(scope, "Py3EnumEmpty"); - py::py3_enum(m, "Py3Enum") + auto e = py::py3_enum(m, "Py3Enum") .value("A", Py3Enum::A) .value("B", Py3Enum::B) - .value("C", Py3Enum::C); + .value("C", Py3Enum::C) + .extend() + .def("add", [](Py3Enum x, int y) { return static_cast(x) + y; }) + .def_property_readonly("is_b", [](Py3Enum e) { return e == Py3Enum::B; }) + .def_property_readonly_static("ultimate_answer", [](py::object) { return 42; }); py::py3_enum(m, "Py3EnumScoped") .value("X", Py3EnumScoped::X) diff --git a/tests/test_enum.py b/tests/test_enum.py index 749de49e2d..00962cb337 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -164,3 +164,7 @@ def test_py3_enum(): with pytest.raises(ValueError) as excinfo: non_unique_py3_enum() assert 'duplicate values found' in str(excinfo.value) + + assert Py3Enum.ultimate_answer == 42 + assert not Py3Enum.A.is_b and Py3Enum.B.is_b and not Py3Enum.C.is_b + assert Py3Enum.A.add(10) == -32 and Py3Enum.C.add(-1) == 41 From cf1ef95a311547d55c755d990b9e725beb4df6a4 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Mon, 1 May 2017 18:48:38 +0100 Subject: [PATCH 08/11] Move public part of class_ to class_interface --- include/pybind11/pybind11.h | 307 ++++++++++++++++++++---------------- 1 file changed, 171 insertions(+), 136 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index d21ad2ac13..846ececc2a 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -39,6 +39,8 @@ NAMESPACE_BEGIN(pybind11) +template class class_; + /// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object class cpp_function : public function { public: @@ -891,226 +893,259 @@ void call_operator_delete(T *p) { T::operator delete(p); } inline void call_operator_delete(void *p) { ::operator delete(p); } -NAMESPACE_END(detail) - -template -class class_ : public detail::generic_type { - template using is_holder = detail::is_holder_type; - template using is_subtype = detail::bool_constant::value && !std::is_same::value>; - template using is_base = detail::bool_constant::value && !std::is_same::value>; - // struct instead of using here to help MSVC: - template struct is_valid_class_option : - detail::any_of, is_subtype, is_base> {}; +template +class class_interface { + using cls = typename std::conditional< + std::is_convertible&>::value, + class_&, + class_ + >::type; public: - using type = type_; - using type_alias = detail::exactly_one_t; - constexpr static bool has_alias = !std::is_void::value; - using holder_type = detail::exactly_one_t, options...>; - using instance_type = detail::instance; - - static_assert(detail::all_of...>::value, - "Unknown/invalid class_ template parameters provided"); - - PYBIND11_OBJECT(class_, generic_type, PyType_Check) - - template - class_(handle scope, const char *name, const Extra &... extra) { - using namespace detail; - - // MI can only be specified via class_ template options, not constructor parameters - static_assert( - none_of...>::value || // no base class arguments, or: - ( constexpr_sum(is_pyobject::value...) == 1 && // Exactly one base - constexpr_sum(is_base::value...) == 0 && // no template option bases - none_of...>::value), // no multiple_inheritance attr - "Error: multiple inheritance bases must be specified via class_ template options"); - - type_record record; - record.scope = scope; - record.name = name; - record.type = &typeid(type); - record.type_size = sizeof(conditional_t); - record.instance_size = sizeof(instance_type); - record.init_holder = init_holder; - record.dealloc = dealloc; - record.default_holder = std::is_same>::value; - - set_operator_new(&record); - - /* Register base classes specified via template arguments to class_, if any */ - bool unused[] = { (add_base(record), false)..., false }; - (void) unused; - - /* Process optional arguments, if any */ - process_attributes::init(extra..., &record); - - generic_type::initialize(record); - - if (has_alias) { - auto &instances = get_internals().registered_types_cpp; - instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))]; - } - } - - template ::value, int> = 0> - static void add_base(detail::type_record &rec) { - rec.add_base(&typeid(Base), [](void *src) -> void * { - return static_cast(reinterpret_cast(src)); - }); - } - - template ::value, int> = 0> - static void add_base(detail::type_record &) { } - template - class_ &def(const char *name_, Func&& f, const Extra&... extra) { - cpp_function cf(std::forward(f), name(name_), is_method(*this), - sibling(getattr(*this, name_, none())), extra...); - attr(cf.name()) = cf; - return *this; + cls def(const char *name_, Func&& f, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + cpp_function cf(std::forward(f), name(name_), is_method(self), + sibling(getattr(self, name_, none())), extra...); + self.attr(cf.name()) = cf; + return self; } - template class_ & - def_static(const char *name_, Func &&f, const Extra&... extra) { + template + cls def_static(const char *name_, Func &&f, const Extra&... extra) { static_assert(!std::is_member_function_pointer::value, "def_static(...) called with a non-static member function pointer"); - cpp_function cf(std::forward(f), name(name_), scope(*this), - sibling(getattr(*this, name_, none())), extra...); - attr(cf.name()) = cf; - return *this; + cls self = static_cast(static_cast(*this)); + cpp_function cf(std::forward(f), name(name_), scope(self), + sibling(getattr(self, name_, none())), extra...); + self.attr(cf.name()) = cf; + return self; } - template - class_ &def(const detail::op_ &op, const Extra&... extra) { - op.execute(*this, extra...); - return *this; + template + cls def(const op_ &op, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + op.execute(self, extra...); + return self; } - template - class_ & def_cast(const detail::op_ &op, const Extra&... extra) { - op.execute_cast(*this, extra...); - return *this; + template + cls def_cast(const op_ &op, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + op.execute_cast(self, extra...); + return self; } template - class_ &def(const detail::init &init, const Extra&... extra) { - init.execute(*this, extra...); - return *this; + cls def(const init &init, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + init.execute(self, extra...); + return self; } template - class_ &def(const detail::init_alias &init, const Extra&... extra) { - init.execute(*this, extra...); - return *this; + cls def(const init_alias &init, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + init.execute(self, extra...); + return self; } - template class_& def_buffer(Func &&func) { + template + cls def_buffer(Func &&func) { + cls self = static_cast(static_cast(*this)); struct capture { Func func; }; capture *ptr = new capture { std::forward(func) }; - install_buffer_funcs([](PyObject *obj, void *ptr) -> buffer_info* { + self.install_buffer_funcs([](PyObject *obj, void *ptr) -> buffer_info* { detail::make_caster caster; if (!caster.load(obj, false)) return nullptr; return new buffer_info(((capture *) ptr)->func(caster)); }, ptr); - return *this; + return self; } template - class_ &def_readwrite(const char *name, D C::*pm, const Extra&... extra) { - cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(*this)), - fset([pm](C &c, const D &value) { c.*pm = value; }, is_method(*this)); - def_property(name, fget, fset, return_value_policy::reference_internal, extra...); - return *this; + cls def_readwrite(const char *name, D C::*pm, const Extra&... extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(self)), + fset([pm](C &c, const D &value) { c.*pm = value; }, is_method(self)); + self.def_property(name, fget, fset, return_value_policy::reference_internal, extra...); + return self; } template - class_ &def_readonly(const char *name, const D C::*pm, const Extra& ...extra) { - cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(*this)); - def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); - return *this; + cls def_readonly(const char *name, const D C::*pm, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](const C &c) -> const D &{ return c.*pm; }, is_method(self)); + self.def_property_readonly(name, fget, return_value_policy::reference_internal, extra...); + return self; } template - class_ &def_readwrite_static(const char *name, D *pm, const Extra& ...extra) { - cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(*this)), - fset([pm](object, const D &value) { *pm = value; }, scope(*this)); - def_property_static(name, fget, fset, return_value_policy::reference, extra...); - return *this; + cls def_readwrite_static(const char *name, D *pm, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(self)), + fset([pm](object, const D &value) { *pm = value; }, scope(self)); + self.def_property_static(name, fget, fset, return_value_policy::reference, extra...); + return self; } template - class_ &def_readonly_static(const char *name, const D *pm, const Extra& ...extra) { - cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(*this)); - def_property_readonly_static(name, fget, return_value_policy::reference, extra...); - return *this; + cls def_readonly_static(const char *name, const D *pm, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + cpp_function fget([pm](object) -> const D &{ return *pm; }, scope(self)); + self.def_property_readonly_static(name, fget, return_value_policy::reference, extra...); + return self; } /// Uses return_value_policy::reference_internal by default template - class_ &def_property_readonly(const char *name, const Getter &fget, const Extra& ...extra) { - return def_property_readonly(name, cpp_function(fget), return_value_policy::reference_internal, extra...); + cls def_property_readonly(const char *name, const Getter &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_readonly(name, cpp_function(fget), return_value_policy::reference_internal, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property_readonly(const char *name, const cpp_function &fget, const Extra& ...extra) { - return def_property(name, fget, cpp_function(), extra...); + cls def_property_readonly(const char *name, const cpp_function &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property(name, fget, cpp_function(), extra...); } /// Uses return_value_policy::reference by default template - class_ &def_property_readonly_static(const char *name, const Getter &fget, const Extra& ...extra) { - return def_property_readonly_static(name, cpp_function(fget), return_value_policy::reference, extra...); + cls def_property_readonly_static(const char *name, const Getter &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_readonly_static(name, cpp_function(fget), return_value_policy::reference, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property_readonly_static(const char *name, const cpp_function &fget, const Extra& ...extra) { - return def_property_static(name, fget, cpp_function(), extra...); + cls def_property_readonly_static(const char *name, const cpp_function &fget, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_static(name, fget, cpp_function(), extra...); } /// Uses return_value_policy::reference_internal by default template - class_ &def_property(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property(name, cpp_function(fget), fset, return_value_policy::reference_internal, extra...); + cls def_property(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property(name, cpp_function(fget), fset, return_value_policy::reference_internal, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property_static(name, fget, fset, is_method(*this), extra...); + cls def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_static(name, fget, fset, is_method(self), extra...); } /// Uses return_value_policy::reference by default template - class_ &def_property_static(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { - return def_property_static(name, cpp_function(fget), fset, return_value_policy::reference, extra...); + cls def_property_static(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + return self.def_property_static(name, cpp_function(fget), fset, return_value_policy::reference, extra...); } /// Uses cpp_function's return_value_policy by default template - class_ &def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { - auto rec_fget = get_function_record(fget), rec_fset = get_function_record(fset); + cls def_property_static(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) { + cls self = static_cast(static_cast(*this)); + auto rec_fget = self.get_function_record(fget), rec_fset = self.get_function_record(fset); char *doc_prev = rec_fget->doc; /* 'extra' field may include a property-specific documentation string */ - detail::process_attributes::init(extra..., rec_fget); + process_attributes::init(extra..., rec_fget); if (rec_fget->doc && rec_fget->doc != doc_prev) { free(doc_prev); rec_fget->doc = strdup(rec_fget->doc); } if (rec_fset) { doc_prev = rec_fset->doc; - detail::process_attributes::init(extra..., rec_fset); + process_attributes::init(extra..., rec_fset); if (rec_fset->doc && rec_fset->doc != doc_prev) { free(doc_prev); rec_fset->doc = strdup(rec_fset->doc); } } - def_property_static_impl(name, fget, fset, rec_fget); - return *this; + self.def_property_static_impl(name, fget, fset, rec_fget); + return self; + } +}; + +NAMESPACE_END(detail) + +template +class class_ : public detail::generic_type, + public detail::class_interface, type_, options...> { + template using is_holder = detail::is_holder_type; + template using is_subtype = detail::bool_constant::value && !std::is_same::value>; + template using is_base = detail::bool_constant::value && !std::is_same::value>; + // struct instead of using here to help MSVC: + template struct is_valid_class_option : + detail::any_of, is_subtype, is_base> {}; + + friend detail::class_interface; + +public: + using type = type_; + using type_alias = detail::exactly_one_t; + constexpr static bool has_alias = !std::is_void::value; + using holder_type = detail::exactly_one_t, options...>; + using instance_type = detail::instance; + + static_assert(detail::all_of...>::value, + "Unknown/invalid class_ template parameters provided"); + + PYBIND11_OBJECT(class_, generic_type, PyType_Check) + + template + class_(handle scope, const char *name, const Extra &... extra) { + using namespace detail; + + // MI can only be specified via class_ template options, not constructor parameters + static_assert( + none_of...>::value || // no base class arguments, or: + ( constexpr_sum(is_pyobject::value...) == 1 && // Exactly one base + constexpr_sum(is_base::value...) == 0 && // no template option bases + none_of...>::value), // no multiple_inheritance attr + "Error: multiple inheritance bases must be specified via class_ template options"); + + type_record record; + record.scope = scope; + record.name = name; + record.type = &typeid(type); + record.type_size = sizeof(conditional_t); + record.instance_size = sizeof(instance_type); + record.init_holder = init_holder; + record.dealloc = dealloc; + record.default_holder = std::is_same>::value; + + set_operator_new(&record); + + /* Register base classes specified via template arguments to class_, if any */ + bool unused[] = { (add_base(record), false)..., false }; + (void) unused; + + /* Process optional arguments, if any */ + process_attributes::init(extra..., &record); + + generic_type::initialize(record); + + if (has_alias) { + auto &instances = get_internals().registered_types_cpp; + instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))]; + } } + template ::value, int> = 0> + static void add_base(detail::type_record &rec) { + rec.add_base(&typeid(Base), [](void *src) -> void * { + return static_cast(reinterpret_cast(src)); + }); + } + + template ::value, int> = 0> + static void add_base(detail::type_record &) { } + private: /// Initialize holder object, variant 1: object derives from enable_shared_from_this template From 8088a26ad832a7c8c01b7e46aa19732f7de2955a Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Tue, 2 May 2017 20:28:36 +0100 Subject: [PATCH 09/11] Derive py3_enum<> from class_interface<> --- include/pybind11/pybind11.h | 15 +++++++-------- tests/test_enum.cpp | 1 - 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 846ececc2a..fa378b7449 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1293,7 +1293,7 @@ template class enum_ : public class_ { }; template -class py3_enum { +class py3_enum : public detail::class_interface, T> { public: using underlying_type = typename std::underlying_type::type; @@ -1317,17 +1317,13 @@ class py3_enum { update(); } - py3_enum& value(const char* name, T value) & { + py3_enum& value(const char* name, T value) { add_entry(name, value); return *this; } - py3_enum&& value(const char* name, T value) && { - add_entry(name, value); - return std::move(*this); - } - - class_ extend() && { + operator class_() { + locked = true; return cast>(type); } @@ -1339,8 +1335,11 @@ class py3_enum { object unique; dict kwargs; object type; + bool locked = false; void add_entry(const char *name, T value) { + if (locked) + throw std::runtime_error("Unable to modify a locked enum class"); entries[name] = cast(static_cast(value)); update(); } diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index f157f61640..8bf19d157c 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -86,7 +86,6 @@ test_initializer enums([](py::module &m) { .value("A", Py3Enum::A) .value("B", Py3Enum::B) .value("C", Py3Enum::C) - .extend() .def("add", [](Py3Enum x, int y) { return static_cast(x) + y; }) .def_property_readonly("is_b", [](Py3Enum e) { return e == Py3Enum::B; }) .def_property_readonly_static("ultimate_answer", [](py::object) { return 42; }); From 500e3f42daf31fe32f96153346c799e52e4d0913 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 3 May 2017 00:27:55 +0100 Subject: [PATCH 10/11] (Pacify flake8) --- tests/test_enum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_enum.py b/tests/test_enum.py index 00962cb337..96e1e40720 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -136,7 +136,7 @@ def test_py3_enum(): make_py3_enum, take_py3_enum, non_unique_py3_enum ) - Py3EnumEmpty = DummyScope.Py3EnumEmpty + Py3EnumEmpty = DummyScope.Py3EnumEmpty # noqa from enum import IntEnum From a2b3e9cf73d1ac25f70c21034ca5bdeef6488f28 Mon Sep 17 00:00:00 2001 From: Ivan Smirnov Date: Wed, 3 May 2017 00:43:30 +0100 Subject: [PATCH 11/11] Implement py3_enum::export_values() --- include/pybind11/pybind11.h | 20 +++++++++++++++++--- tests/test_enum.cpp | 8 +++++--- tests/test_enum.py | 11 +++++++---- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index fa378b7449..e816b3f894 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1317,14 +1317,18 @@ class py3_enum : public detail::class_interface, T> { update(); } + /// Add an enumeration entry py3_enum& value(const char* name, T value) { add_entry(name, value); return *this; } - operator class_() { - locked = true; - return cast>(type); + /// Export enumeration entries into the parent scope + py3_enum& export_values() { + for (const auto &kv : entries) + scope.attr(kv.first) = type.attr(kv.first); + reexport = true; + return *this; } private: @@ -1336,6 +1340,7 @@ class py3_enum : public detail::class_interface, T> { dict kwargs; object type; bool locked = false; + bool reexport = false; void add_entry(const char *name, T value) { if (locked) @@ -1347,8 +1352,17 @@ class py3_enum : public detail::class_interface, T> { void update() { type = unique(ctor(**kwargs)); setattr(scope, name, type); + if (reexport) + export_values(); detail::type_caster::bind(type, entries); } + + operator class_() { + locked = true; + return cast>(type); + } + + friend detail::class_interface, T>; }; NAMESPACE_BEGIN(detail) diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 8bf19d157c..459ee2b015 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -79,8 +79,7 @@ test_initializer enums([](py::module &m) { .export_values(); #if PY_VERSION_HEX >= 0x03000000 - auto scope = py::class_(m, "DummyScope"); - py::py3_enum(scope, "Py3EnumEmpty"); + py::py3_enum(m, "Py3EnumEmpty"); auto e = py::py3_enum(m, "Py3Enum") .value("A", Py3Enum::A) @@ -90,8 +89,11 @@ test_initializer enums([](py::module &m) { .def_property_readonly("is_b", [](Py3Enum e) { return e == Py3Enum::B; }) .def_property_readonly_static("ultimate_answer", [](py::object) { return 42; }); - py::py3_enum(m, "Py3EnumScoped") + auto scope = py::class_(m, "DummyScope"); + + py::py3_enum(scope, "Py3EnumScoped") .value("X", Py3EnumScoped::X) + .export_values() .value("Y", Py3EnumScoped::Y); m.def("make_py3_enum", [](bool x) { diff --git a/tests/test_enum.py b/tests/test_enum.py index 96e1e40720..c0a6ee9327 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -132,11 +132,11 @@ def test_enum_to_int(): @pytest.requires_py3 def test_py3_enum(): from pybind11_tests import ( - Py3Enum, DummyScope, Py3EnumScoped, + Py3Enum, DummyScope, Py3EnumEmpty, make_py3_enum, take_py3_enum, non_unique_py3_enum ) - Py3EnumEmpty = DummyScope.Py3EnumEmpty # noqa + Py3EnumScoped = DummyScope.Py3EnumScoped # noqa from enum import IntEnum @@ -151,9 +151,12 @@ def test_py3_enum(): assert sorted(tp.__members__.items()) == entries assert tp.__module__ == 'pybind11_tests' + assert DummyScope.X is Py3EnumScoped.X + assert DummyScope.Y is Py3EnumScoped.Y + assert Py3Enum.__qualname__ == 'Py3Enum' - assert Py3EnumEmpty.__qualname__ == 'DummyScope.Py3EnumEmpty' - assert Py3EnumScoped.__qualname__ == 'Py3EnumScoped' + assert Py3EnumEmpty.__qualname__ == 'Py3EnumEmpty' + assert Py3EnumScoped.__qualname__ == 'DummyScope.Py3EnumScoped' assert make_py3_enum(True) is Py3EnumScoped.X assert make_py3_enum(False) is Py3EnumScoped.Y