How to enumerate enum members using SWIG

2020-02-09 08:44发布

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?

标签: c++ python swig
3条回答
劳资没心,怎么记你
2楼-- · 2020-02-09 09:21

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.

查看更多
放我归山
3楼-- · 2020-02-09 09:31

I'm pretty sure you're looking for are...
typemaps: Since type handling is so central to wrapper code generation, SWIG allows it to be completely defined (or redefined) by the user. To do this, a special %typemap directive is used. (SWIG Doc2.0)

For all the info you could ever need about typemaps here is the link to the SWIG documentation about it. http://www.swig.org/Doc2.0/Typemaps.html#Typemaps_nn2

Typemaps should allow you to tell SWIG to convert c++ enums to python objects that you want.

查看更多
该账号已被封号
4楼-- · 2020-02-09 09:32

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}
>>> 
查看更多
登录 后发表回答