Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enumerate enum members using SWIG

Tags:

c++

python

swig

Can I expose a C++ enum to SWIG as a real entity rather than a set of constants so I can enumerate over them in python code?

like image 419
unkulunkulu Avatar asked Nov 07 '12 12:11

unkulunkulu


People also ask

What is Unscoped enumeration?

In an unscoped enum, the scope is the surrounding scope; in a scoped enum, the scope is the enum-list itself. In a scoped enum, the list may be empty, which in effect defines a new integral type. class. By using this keyword in the declaration, you specify the enum is scoped, and an identifier must be provided.

How does enumerate work in C++?

In C++ programming, enum or enumeration is a data type consisting of named values like elements, members, etc., that represent integral constants. It provides a way to define and group integral constants. It also makes the code easy to maintain and less complex.

Can two enum names have the same value?

Two enum names can have same value. For example, in the following C program both 'Failed' and 'Freezed' have same value 0.

What is the use of enumerations?

Enumerations make for clearer and more readable code, particularly when meaningful names are used. The benefits of using enumerations include: Reduces errors caused by transposing or mistyping numbers. Makes it easy to change values in the future.


2 Answers

I faced the same issue. I hope that SWIG soon supports C++11's enum class.

Here's a hack that convinces SWIG to put enums in a structure:

#ifdef SWIG
%rename(MyEnum) MyEnumNS;
#endif

struct MyEnumNS
{
    enum Value { Value1, Value2, Value3 };
};
typedef MyEnumNS::Value MyEnum;

In .cpp code you now must use MyEnum::Value1 and in Python code it is MyEnum.Value1. Although convoluted, the typedef prevents having to change existing code that uses the enum everywhere and the SWIG %rename makes the enum have the same name in the SWIG wrapper.

In Python you can enumerate the values with a little code:

def values(enum):
    return [(k,v) for k,v in vars(enum).items() if isinstance(v,int)]

It's not pretty, and I'd love to see a better solution.

like image 69
Mark Tolonen Avatar answered Sep 28 '22 03:09

Mark Tolonen


We can make something that lets you enumerate over it in Python, with relatively little intrusion into the C++ headers it wraps. For example if we have a header file:

#ifndef PYTHON_ENUM 
#define PYTHON_ENUM(x) enum x
#endif

PYTHON_ENUM(TestName) {
  foo=1,
  bar=2
};

PYTHON_ENUM(SomeOtherName) {
  woof,
  moo
};

It expands to be just a regular enum in C++, but is sufficient as a header file to expose the enum members in Python.

Using %typemap(constcode) we can inject some extra things into our Python module for the enum, but we need to know the name of the enum to do this; The SWIG typeinfo object for it is just as if it were an int. Therefore we use a bit of a hack in our PYTHON_ENUM macro to store the name of the enum in a custom typemap.

%module test
%{
#include "test.h"
%}

%typemap(constcode) int {
  PyObject *val = PyInt_FromLong(($type)($value));
  SWIG_Python_SetConstant(d, "$1", val);
  const char *name = "$typemap(enum_realname,$1_type)";
  PyObject *e = PyDict_GetItemString(d, name);
  if (!e) PyDict_SetItemString(d, name, e = PyDict_New());
  PyDict_SetItemString(e, "$value", val);
}
#define PYTHON_ENUM(x) \
        %typemap(enum_realname) int "x"; \
        %pythoncode %{ \
        x = _test.x\
        %} \
        enum x

%include "test.h"

This creates a PyDict in the intermediate module for every enum that has key/value pairs. There's also some %pythoncode glue there to tie the PyDict in the intermediate module into the exposed module. (I'm not sure how to refer to the intermediate module by name in it, other than hardcoded as _test - change as needed).

This is sufficient that I can then use it as:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.SomeOtherName
{'woof': 0, 'moo': 1}
>>> print test.TestName
{'foo': 1, 'bar': 2}
>>> 
like image 28
Flexo Avatar answered Sep 28 '22 04:09

Flexo