<p><em>编辑说明:</em><a href="https://stackoverflow.com/a/69281896/4657412">An answer on a very similar question</a>详细说明了<code>enum.Enum</code>如何具有可替代使用的功能接口。这几乎肯定是正确的方法。我认为我在这里的回答是一个值得注意的有用的替代方法,尽管它可能不是这个问题的最佳解决方案</p>
<hr/>
<p>我知道这个答案有点假,但这正是最好用Python编写的代码,在C API中,我们仍然可以访问完整的Python解释器。我的理由是,将所有内容完全保留在C中的主要原因是性能,而且创建枚举对象似乎不太可能是性能关键</p>
<p>我将给出三个版本,基本上取决于复杂程度</p>
<hr/>
<p>首先,最简单的情况是:枚举是完全已知的,并且是在编译时定义的。在这里,我们只需设置一个空的全局dict,运行Python代码,然后从全局dict中提取枚举:</p>
<pre class="lang-c prettyprint-override"><code>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;
}
</code></pre>
<hr/>
<p>其次,我们可能希望在运行时更改在C中定义的内容。例如,输入参数可能会选择枚举值。在这里,我将使用字符串格式将适当的值插入到字符串中。这里有很多选项:^ {< CD2> },^ {CD3}},C++标准库,使用Python字符串(也许是用另一个调用到Python代码中)。选择你最喜欢的</p>
<pre class="lang-c prettyprint-override"><code>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;
}
</code></pre>
<p>显然,您可以对字符串格式做任意多或任意少的操作—我刚刚选择了一个简单的示例来说明这一点。与上一版本的主要区别是对<code>PyBytes_FromFormat</code>的调用以设置字符串,以及对<code>PyBytes_AsString</code>的调用,该调用从准备好的<code>bytes</code>对象中获取底层<code>char*</code></p>
<hr/>
<p>最后,我们可以在C Python<code>dict</code>中准备enum属性并将其传入。这需要一些改变。实际上,我使用@AnttiHaapala的低级Python代码,但在调用<code>__prepare__</code>之后插入<code>namespace.update(contents)</code></p>
<pre class="lang-c prettyprint-override"><code>
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;
}
</code></pre>
<p>同样,这提供了一种相当灵活的方法,可以将值从C获取到生成的枚举中</p>
<hr/>
<p>为了进行测试,我使用了下面这个简单的Cython包装器——这只是为了完整性,以帮助人们尝试这些功能</p>
<pre><code>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)
</code></pre>
<hr/>
<p>重申一下:这个答案只在C API中有一部分,但我发现从C调用Python的方法有时对完全用C编写的“运行一次”代码很有效</p>