如何在Python C API中创建枚举对象?

2024-09-29 22:25:47 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在为如何在python C API中创建python枚举对象而苦苦挣扎。enum类已将tp_base分配给PyEnum_Type,因此它继承了enum。但是,我想不出一种方法来告诉枚举基类枚举中有哪些项。我想允许使用每个Python枚举提供的__members__属性从Python进行迭代和查找

谢谢,

杰尔


Tags: 对象方法apibase属性typeenum基类
2条回答

编辑说明: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中定义的内容。例如,输入参数可能会选择枚举值。在这里,我将使用字符串格式将适当的值插入到字符串中。这里有很多选项:^ {< CD2> },^ {CD3}},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 Pythondict中准备enum属性并将其传入。这需要一些改变。实际上,我使用@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编写的“运行一次”代码很有效

这一点也不简单。{}是一个使用Python元类Python类。可以在C中创建它,但它只是模拟C中构造Python代码的过程,最终结果是一样的,虽然它稍微加快了速度,但您很可能在每次程序运行中只运行一次代码

无论如何,这是可能的,但一点也不容易。我将演示如何在Python中执行此操作:

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

print(Color)
print(Color.RED)

同:

from enum import Enum

name = 'Color'
bases = (Enum,)
enum_meta = type(Enum)

namespace = enum_meta.__prepare__(name, bases)
namespace['RED'] = 1
namespace['GREEN'] = 2
namespace['BLUE'] = 3

Color = enum_meta(name, bases, namespace)

print(Color)
print(Color.RED)

后者是需要翻译成C的代码

相关问题 更多 >

    热门问题