Skip to content

enum_ QoL improvements and enum.IntEnum #530

Closed
@aldanor

Description

@aldanor

I've been using Python 3 enum.IntEnum lately instead of pybind11 enum_<> enums (although they're less straightforward to bind), since the former are (a) faster and (b) more ergonomic/Pythonic.

I wonder if we could either improve enum_ a little (a lot?), and/or provide a shortcut to bind standard library enums on Python 3?

I'll try to prove my point using this mini-example:

#include <pybind11/pybind11.h>

#include <stdexcept>

enum class Side1 : char { Right = 1, Left = -1 };
enum class Side2 : char { Right = 1, Left = -1 };

namespace pybind11 { namespace detail {
template<> struct type_caster<::Side1> {
    static handle cast(const ::Side1& src, return_value_policy, handle) {
        // this is the only flaky-ish part here; a better way to share `test_enums.Side1`?
        static object cls = module::import("test_enums").attr("Side1");
        if (!cls) throw std::runtime_error("unable to import test_enums.Side1");

        static object v_right = cls.attr("Right"), v_left = cls.attr("Left");

        switch (src) {
        case ::Side1::Right: return v_right.inc_ref();
        case ::Side1::Left: return v_left.inc_ref();
        default: return {};
        }
    }

    static PYBIND11_DESCR name() { return _("Side1"); }
};
} }

PYBIND11_PLUGIN(test_enums) {
    namespace py = pybind11;
    using namespace pybind11::literals;
    py::module m("test_enums");
    m.attr("Side1") = py::module::import("enum").attr("IntEnum")
        ("Side1", py::dict("Right"_a=1, "Left"_a=-1));
    py::enum_<Side2>(m, "Side2")
        .value("Right", Side2::Right).value("Left", Side2::Left);
    m.def("f1", []() { return Side1::Left; });
    m.def("f2", []() { return Side2::Left; });
    return m.ptr();
}
  1. Casting is 3x-4x faster:
>>> from test_enums import *
>>> %timeit f1()
10000000 loops, best of 3: 84.7 ns per loop
>>> %timeit f2()
1000000 loops, best of 3: 305 ns per loop
  1. Comparison is 4x-5x faster:
>>> e1, e2 = f1(), f2()
>>> %timeit e1 == e1
10000000 loops, best of 3: 40 ns per loop
>>> %timeit e2 == e2
1000000 loops, best of 3: 212 ns per loop
  1. singleton values versus new instances:
>>> f1() is f1()
True
>>> f2() is f2()
False
  1. enum.IntEnum type:
>>> list(Side1)
[<Side1.Left: -1>, <Side1.Right: 1>]  # ease of enumeration over values
>>> dict(Side1.__members__)
{'Left': <Side1.Left: -1>, 'Right': <Side1.Right: 1>}
  1. enum_ values:
>>> Side2.Left.Left.Left.Left.Left.Left.Left  # stop showing up in my tab completion!
Side2.Left
>>> int(Side2.Left)
TypeError: __int__ returned non-int (type str)  # because `char` caster, I guess...
>>> isinstance(Side2.Left, int)
False
  1. enum.IntEnum values:
>>> isinstance(Side1.left, int)  # no need to reinvent the wheel, IntEnum is a subclass of int
True
>>> Side1.Left.name
'Left'
>>> Side1.Left.value
-1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions