编辑说明:An answer on a very similar question 详细说明了enum.Enum 如何具有可替代使用的功能接口。这几乎可以肯定是正确的方法。我认为我在这里的回答是一种有用的替代方法,尽管它可能不是解决这个问题的最佳方法。
我知道这个答案有点作弊,但这正是用 Python 编写的更好的代码,在 C API 中,我们仍然可以访问完整的 Python 解释器。我对此的理由是,将内容完全保留在 C 中的主要原因是性能,而且创建枚举对象似乎不太可能对性能至关重要。
我将给出三个版本,基本上取决于复杂程度。
首先,最简单的情况:枚举是完全已知的并在编译时定义。这里我们简单设置一个空的全局dict,运行Python代码,然后从全局dict中提取枚举:
PyObject* get_enum(void) {
const char str[] = "from enum import Enum\n"
"class Colour(Enum):\n"
" RED = 1\n"
" GREEN = 2\n"
" BLUE = 3\n"
"";
PyObject *global_dict=NULL, *should_be_none=NULL, *output=NULL;
global_dict = PyDict_New();
if (!global_dict) goto cleanup;
should_be_none = PyRun_String(str, Py_file_input, global_dict, global_dict);
if (!should_be_none) goto cleanup;
// extract Color from global_dict
output = PyDict_GetItemString(global_dict, "Colour");
if (!output) {
// PyDict_GetItemString does not set exceptions
PyErr_SetString(PyExc_KeyError, "could not get 'Colour'");
} else {
Py_INCREF(output); // PyDict_GetItemString returns a borrow reference
}
cleanup:
Py_XDECREF(global_dict);
Py_XDECREF(should_be_none);
return output;
}
其次,我们可能想在运行时更改我们在 C 中定义的内容。例如,输入参数可能会选择枚举值。在这里,我将使用字符串格式化将适当的值插入到我们的字符串中。这里有许多选项:sprintf、PyBytes_Format,C++ 标准库,使用 Python 字符串(可能还调用 Python 代码?)。选择您最喜欢的。
PyObject* get_enum_fmt(int red, int green, int blue) {
const char str[] = "from enum import Enum\n"
"class Colour(Enum):\n"
" RED = %d\n"
" GREEN = %d\n"
" BLUE = %d\n"
"";
PyObject *formatted_str=NULL, *global_dict=NULL, *should_be_none=NULL, *output=NULL;
formatted_str = PyBytes_FromFormat(str, red, green, blue);
if (!formatted_str) goto cleanup;
global_dict = PyDict_New();
if (!global_dict) goto cleanup;
should_be_none = PyRun_String(PyBytes_AsString(formatted_str), Py_file_input, global_dict, global_dict);
if (!should_be_none) goto cleanup;
// extract Color from global_dict
output = PyDict_GetItemString(global_dict, "Colour");
if (!output) {
// PyDict_GetItemString does not set exceptions
PyErr_SetString(PyExc_KeyError, "could not get 'Colour'");
} else {
Py_INCREF(output); // PyDict_GetItemString returns a borrow reference
}
cleanup:
Py_XDECREF(formatted_str);
Py_XDECREF(global_dict);
Py_XDECREF(should_be_none);
return output;
}
显然,您可以随心所欲地使用字符串格式——我刚刚选择了一个简单的例子来说明这一点。与之前版本的主要区别在于调用PyBytes_FromFormat 来设置字符串,以及调用PyBytes_AsString 从准备好的bytes 对象中获取底层char*。
最后,我们可以在 C Python dict 中准备枚举属性并将其传入。这需要进行一些更改。本质上我使用@AnttiHaapala 的低级Python 代码,但在调用__prepare__ 之后插入namespace.update(contents)。
PyObject* get_enum_dict(const char* key1, int value1, const char* key2, int value2) {
const char str[] = "from enum import Enum\n"
"name = 'Colour'\n"
"bases = (Enum,)\n"
"enum_meta = type(Enum)\n"
"namespace = enum_meta.__prepare__(name, bases)\n"
"namespace.update(contents)\n"
"Colour = enum_meta(name, bases, namespace)\n";
PyObject *global_dict=NULL, *contents_dict=NULL, *value_as_object=NULL, *should_be_none=NULL, *output=NULL;
global_dict = PyDict_New();
if (!global_dict) goto cleanup;
// create and fill the contents dictionary
contents_dict = PyDict_New();
if (!contents_dict) goto cleanup;
value_as_object = PyLong_FromLong(value1);
if (!value_as_object) goto cleanup;
int set_item_result = PyDict_SetItemString(contents_dict, key1, value_as_object);
Py_CLEAR(value_as_object);
if (set_item_result!=0) goto cleanup;
value_as_object = PyLong_FromLong(value2);
if (!value_as_object) goto cleanup;
set_item_result = PyDict_SetItemString(contents_dict, key2, value_as_object);
Py_CLEAR(value_as_object);
if (set_item_result!=0) goto cleanup;
set_item_result = PyDict_SetItemString(global_dict, "contents", contents_dict);
if (set_item_result!=0) goto cleanup;
should_be_none = PyRun_String(str, Py_file_input, global_dict, global_dict);
if (!should_be_none) goto cleanup;
// extract Color from global_dict
output = PyDict_GetItemString(global_dict, "Colour");
if (!output) {
// PyDict_GetItemString does not set exceptions
PyErr_SetString(PyExc_KeyError, "could not get 'Colour'");
} else {
Py_INCREF(output); // PyDict_GetItemString returns a borrow reference
}
cleanup:
Py_XDECREF(contents_dict);
Py_XDECREF(global_dict);
Py_XDECREF(should_be_none);
return output;
}
同样,这提供了一种相当灵活的方法来将 C 中的值获取到生成的枚举中。
为了测试,我使用了以下简单的 Cython 包装器 - 这只是为了帮助人们尝试这些功能的完整性。
cdef extern from "cenum.c":
object get_enum()
object get_enum_fmt(int, int, int)
object get_enum_dict(char*, int, char*, int)
def py_get_enum():
return get_enum()
def py_get_enum_fmt(red, green, blue):
return get_enum_fmt(red, green, blue)
def py_get_enum_dict(key1, value1, key2, value2):
return get_enum_dict(key1, value1, key2, value2)
重申一下:这个答案只是部分在 C API 中,但是从 C 调用 Python 的方法是我发现有时对于完全用 C 编写的“运行一次”代码很有成效的方法.