在 C 扩展中定义 Python 枚举 - 我这样做对吗?

2024-01-21

我正在开发一个 Python C 扩展,我想公开一个完全在 C 中定义的自定义枚举(如:继承自 enum.Enum 的类)。

事实证明这不是一个简单的任务,并且使用常规的继承机制.tp_base不起作用 - 很可能是由于 Enum 的元类没有被引入。

基本上我正在尝试这样做:

import enum

class FooBar(enum.Enum):
    FOO = 1
    BAR = 2

in C.

经过大量深入研究 cpython 的内部结构后,这就是我想到的,包装在一个示例可构建模块中:

#include <Python.h>

PyDoc_STRVAR(module_doc,
"C extension module defining a class inheriting from enum.Enum.");

static PyModuleDef module_def = {
    PyModuleDef_HEAD_INIT,
    .m_name = "pycenum",
    .m_doc = module_doc,
    .m_size = -1,
};

struct enum_descr {
    const char *name;
    long value;
};

static const struct enum_descr foobar_descr[] = {
    {
        .name = "FOO",
        .value = 1,
    },
    {
        .name  = "BAR",
        .value = 2,
    },
    { }
};

static PyObject *make_bases(PyObject *enum_mod)
{
    PyObject *enum_type, *bases;

    enum_type = PyObject_GetAttrString(enum_mod, "Enum");
    if (!enum_type)
        return NULL;

    bases = PyTuple_Pack(1, enum_type); /* Steals reference. */
    if (!bases)
        Py_DECREF(enum_type);

    return bases;
}

static PyObject *make_classdict(PyObject *enum_mod, PyObject *bases)
{
    PyObject *enum_meta_type, *classdict;

    enum_meta_type = PyObject_GetAttrString(enum_mod, "EnumMeta");
    if (!enum_meta_type)
        return NULL;

    classdict = PyObject_CallMethod(enum_meta_type, "__prepare__",
                                    "sO", "FooBarEnum", bases);
    Py_DECREF(enum_meta_type);
    return classdict;
}

static int fill_classdict(PyObject *classdict, PyObject *modname,
                          const struct enum_descr *descr)
{
    const struct enum_descr *entry;
    PyObject *key, *val;
    int ret;

    key = PyUnicode_FromString("__module__");
    if (!key)
        return -1;

    ret = PyObject_SetItem(classdict, key, modname);
    Py_DECREF(key);
    if (ret < 0)
        return -1;

    for (entry = descr; entry->name; entry++) {
        key = PyUnicode_FromString(entry->name);
        if (!key)
            return -1;

        val = PyLong_FromLong(entry->value);
        if (!val) {
            Py_DECREF(key);
            return -1;
        }

        ret = PyObject_SetItem(classdict, key, val);
        Py_DECREF(key);
        Py_DECREF(val);
        if (ret < 0)
            return -1;
    }

    return 0;
}

static PyObject *make_new_type(PyObject *classdict, PyObject *bases,
                               const char *enum_name)
{
    PyObject *name, *args, *new_type;
    int ret;

    name = PyUnicode_FromString(enum_name);
    if (!name)
        return NULL;

    args = PyTuple_Pack(3, name, bases, classdict);
    if (!args) {
        Py_DECREF(name);
        return NULL;
    }

    Py_INCREF(bases);
    Py_INCREF(classdict);
    /*
     * Reference to name was stolen by PyTuple_Pack(), no need to
     * increase it here.
     */

    new_type = PyObject_CallObject((PyObject *)&PyType_Type, args);
    Py_DECREF(args);
    if (!new_type)
        return NULL;

    ret = PyType_Ready((PyTypeObject *)new_type);
    if (ret < 0) {
        Py_DECREF(new_type);
        return NULL;
    }

    return new_type;
}

static PyObject *make_enum_type(PyObject *modname, const char *enum_name,
                                const struct enum_descr *descr)
{
    PyObject *enum_mod, *bases, *classdict, *new_type;
    int ret;

    enum_mod = PyImport_ImportModule("enum");
    if (!enum_mod)
        return NULL;

    bases = make_bases(enum_mod);
    if (!bases) {
        Py_DECREF(enum_mod);
        return NULL;
    }

    classdict = make_classdict(enum_mod, bases);
    if (!classdict) {
        Py_DECREF(bases);
        Py_DECREF(enum_mod);
        return NULL;
    }

    ret = fill_classdict(classdict, modname, descr);
    if (ret < 0) {
        Py_DECREF(bases);
        Py_DECREF(enum_mod);
        Py_DECREF(classdict);
        return NULL;
    }

    new_type = make_new_type(classdict, bases, enum_name);
    Py_DECREF(bases);
    Py_DECREF(enum_mod);
    Py_DECREF(classdict);
    return new_type;
}

PyMODINIT_FUNC PyInit_pycenum(void)
{
    PyObject *module, *modname, *sub_enum_type;
    int ret;

    module = PyModule_Create(&module_def);
    if (!module)
        return NULL;

    ret = PyModule_AddStringConstant(module, "__version__", "0.0.1");
    if (ret < 0) {
        Py_DECREF(module);
        return NULL;
    }

    modname = PyModule_GetNameObject(module);
    if (!modname) {
        Py_DECREF(module);
        return NULL;
    }

    sub_enum_type = make_enum_type(modname, "FooBar", foobar_descr);
    Py_DECREF(modname);
    if (!sub_enum_type) {
        Py_DECREF(module);
        return NULL;
    }

    ret = PyModule_AddObject(module, "FooBar", sub_enum_type);
    if (ret < 0) {
        Py_DECREF(sub_enum_type);
        Py_DECREF(module);
        return NULL;
    }

    return module;
}

基本上我打电话给EnumMeta's __prepare__方法直接创建一个正确的classdict,然后我调用PyType_Type对象也可以创建子类型。

这是可行的,并且 AFAICT 会产生一个行为完全符合预期的类,但是......我这样做对吗?如有任何反馈,我们将不胜感激。


The metaclass in Enum很棘手是的。

但你可以看到here https://docs.python.org/3/library/enum.html#functional-api您可以创建一个枚举(在Python中),例如:

FooBar = enum.Enum('FooBar', dict(FOO=1, BAR=2))

因此,您可以使用此技术在 Python C-API 中轻松创建枚举类,方法如下:

PyObject *key, *val, *name, *attrs, *args, *modname, *kwargs, *enum_type, *sub_enum_type;

attrs = PyDict_New();
key = PyUnicode_FromString("FOO");
val = PyLong_FromLong(1);
PyObject_SetItem(attrs, key, val);
Py_DECREF(key);
Py_DECREF(val);
key = PyUnicode_FromString("BAR");
val = PyLong_FromLong(2);
PyObject_SetItem(attrs, key, val);
Py_DECREF(key);
Py_DECREF(val);
name = PyUnicode_FromString("FooBar");
args = PyTuple_Pack(3, name, attrs);
Py_DECREF(attrs);
Py_DECREF(name);

// the module name might need to be passed as keyword argument
PyDict_Type *kwargs = PyDict_New();
key = PyUnicode_FromString("module");
modname = PyModule_GetNameObject(module);
PyObject_SetItem(kwargs, key, modname);
Py_DECREF(key);
Py_DECREF(modname);

enum_type = PyObject_GetAttrString(enum_mod, "Enum");
sub_enum_type = PyObject_Call(enum_type, args, kwargs)
Py_DECREF(enum_type);
Py_DECREF(args);
Py_DECREF(kwargs);

return sub_enum_type
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 C 扩展中定义 Python 枚举 - 我这样做对吗? 的相关文章

  • 公共领域有哪些替代方案?

    我正在用 java 编写一个游戏 正如问题标题建议的那样 我在类中使用公共字段 暂且 据我所知 公共领域很糟糕 我有一些理解其中的原因 但如果有人能澄清为什么你不应该使用它们 那将不胜感激 问题是 从我所看到的来看 这似乎是合乎逻辑的 是使
  • 如何使用 C# 调用 REST API?

    这是我到目前为止的代码 public class Class1 private const string URL https sub domain com objects json api key 123 private const str
  • 如何使用 DesignData 帮助开发 Metro 应用程序?

    我一直在 Windows Phone 应用程序中愉快地使用 DesignData 我希望使用它来帮助在 VS2012 Blend for VS 中的 Metro 风格应用程序中可视化设计 我已经尝试过希望显而易见的方法
  • const_iterators 更快吗?

    我们的编码指南更喜欢const iterator 因为它们比正常的要快一点iterator 当您使用时 编译器似乎会优化代码const iterator 这真的正确吗 如果是的话 内部到底发生了什么使得const iterator快点 编辑
  • Python 柯里化任意数量的变量

    我正在尝试使用柯里化在 Python 中进行简单的函数添加 我找到了这个咖喱装饰器here https gist github com JulienPalard 021f1c7332507d6a494b def curry func def
  • 如何使 cx-oracle 将查询结果绑定到字典而不是元组?

    这是我的代码 我想找到一种方法将查询结果作为字典列表而不是元组列表返回 看起来 cx oracle 通过部分文档讨论 绑定 来支持这一点 虽然我不知道它是如何工作的 def connect dsn cx Oracle makedsn hos
  • 使用 MessagingCenter 和标准 .NET 事件处理程序向感兴趣的各方通知更改有什么区别?

    使用 MessagingCenter 和标准 NET 事件处理程序向感兴趣的各方通知更改有什么区别 下面演示了同一事物的两个 未经测试的 实现 public class FooClass public event EventHandler
  • 二元运算符重载、隐式类型转换

    class my bool private bool value public my bool bool value value value explicit operator bool return value friend my boo
  • 模板“内联”函数的静态局部变量[重复]

    这个问题在这里已经有答案了 static的局部变量inline如果我的理解是正确的 C 中的函数保证像单个全局变量一样存在 如果inline函数是一个模板 编译器可以在哪里生成该函数的多个版本 下面这篇文章应该很好地回答你的问题 http
  • C++ fill() 与 uninitialized_fill()

    您好 我是初学者 我想知道容器的 fill 和 uninitialized fill 之间的区别 我在谷歌上进行了快速搜索 但没有得到很好的答案 有人可以帮助我吗 fill 将值 使用赋值运算符 分配给已构造的对象 uninitialize
  • Bool类型返回规则

    我使用 dapper ORM 所以我使用两个规则Query
  • 访问结构向量

    我有一个结构 struct OutputStore int myINT string mySTRING 如果我创建一个 OutputStore 类型的数组 如下所示 OutputStore OutputFileData new Output
  • Python,质数检查器[重复]

    这个问题在这里已经有答案了 你好 我正在创建一个函数来检查一个数字是否是素数 但它告诉我 9 是一个素数 def eprimo num if num lt 2 return False if num 2 return True else f
  • C++20 views::join 在生成的嵌套范围::single_view 上进入无限循环

    我正在使用 GCC 实现 v10 2 和 v11 来处理 C 20 范围 测试的行为std views join https en cppreference com w cpp ranges join view 我尝试使用生成嵌套视图sin
  • 为什么 C++ 元组如此奇怪?

    我通常创建自定义structs将不同类型的值分组在一起时 这通常很好 而且我个人发现命名成员访问更容易阅读 但我想创建一个更通用的 API 在其他语言中广泛使用元组后 我想返回类型的值std tuple但发现它们在 C 中使用比在其他语言中
  • 将 .NET 类库(主要定义 CRUD 操作)公开为服务

    公开现有内容的最佳 有效和最快的方法是什么 类 图书馆 主要定义 CRUD 操作 作为service 周转基金服务 or WCF数据服务 以便它可以与银光 or Ajax 在那儿tools 代码生成器 RAD 工具 哪些可以支持这个 预先感
  • PyQt 和 QSignalMapper/lambdas - 多个信号,单槽

    我在 PyQt 的菜单上有一个操作列表 每个操作对应我想要显示的每个不同的提要 所以我有一个 Y 将活动源设置为 Y Z 将其设置为 Z 等等 对于网络漫画阅读程序 我的菜单上都有 并且觉得自动化方法可能更好 而不是每次都打字 类似于将其添
  • 如何从集合中检索元素而不删除它?

    假设如下 gt gt gt s set 1 2 3 我如何获得一个值 任何值 s不做s pop 我想将该项目保留在集合中 直到我确定可以删除它 这只有在异步调用另一个主机之后才能确定 又快又脏 gt gt gt elem s pop gt
  • int 类型的构造函数

    考虑到成本 这些情况是否相同 case 1 int a 5 case 2 int a 5 case 3 int a a 5 这三种语法是不同的 请耐心等待 我使用用户定义类型而不是 int 稍后我将回到 int T a 5 Direct i
  • 致命:所有操作都需要OperationId。请为路径的“获取”操作添加它

    我正在使用 AutoRest 从 swagger json 生成 api 的客户端 输出是 AutoRest code generation utility cli version 3 0 6187 node v10 16 3 max me

随机推荐

  • 概率和百分比的解释

    非常感谢您帮助编写我的模型 如果您不介意的话 我想问您一些编码中的解释 抱歉我不是数学专家 to move ask turtles with gender male if random float 1 lt 0 025 为什么它是 和百分比
  • 在 Shopware 产品列表页面上显示评论计数

    我想要产品列表页面 如产品详细信息页面 上产品评论的总计数 如何在列表页面上获取该计数 这并不是那么微不足道的事情 您需要编写一个插件来实现此目的 在你的插件中你需要创建订阅者 https developer shopware com do
  • 在 matplotlib 中添加颜色条时出现属性错误

    以下代码无法在 Python 2 5 4 上运行 from matplotlib import pylab as pl import numpy as np data np random rand 6 6 fig pl figure 1 f
  • Bash:从文件读取标准输入并将标准输出写入文件

    我正在尝试运行一个应用程序 假设top 因此它将从标准输入的文件中读取并从标准输出写入另一个文件 目前我有 mkfifo stdin pipe tail f stdin pipe top 其按预期工作 然后我就可以echo该文件的内容和顶部
  • 添加 facebook 库后 gradle 错误 DexException

    在我在 gradle 依赖项中添加 fb 库后 dependencies compile com mixpanel android mixpanel android 4 5 2 compile com android support mul
  • VS Code Python 安装和 Python 解释器无法识别

    我在 VS Code 上收到此消息 Python is not installed Please download and install python before using the extension 也没有 Python Inter
  • 创建静态库时嵌入所有外部引用

    我需要为 C 代码创建一个包装器库来包装我的 C 库 有没有一种方法可以创建该包装器库 使得用户只需要链接该包装器库 而不必在链接器命令行上包含所有 C 库 我的测试项目的结构如下所示 lib cpp print cc print h li
  • 适用于 iOS 和 Android 的经典蓝牙包可能吗?

    After learning https stackoverflow com questions 64417509 basic flutter bluetooth questions一般而言 正确了解 BLE 和蓝牙后 我决定使用经典蓝牙来
  • 玩法 2:将表单绑定到 List[Model] 的惯用方法

    我有几个 CRUD 操作要执行 每个操作都针对一组模型 例如比赛日程 球队名单 比赛结果 比赛统计数据等 到目前为止 在我的 Play 体验中 仅几个月 有 1 个项目上线 我一直在使用一对一表单绑定到模型实例 我知道我可以对表单字段名称进
  • ggplot在pdf中嵌入字体

    我一直在使用以下指南来导出用ggplotto pdf 绘图字体指南 http zevross com blog 2014 07 30 tired of using helvetica in your r graphics heres how
  • 如何将 API 响应(json)绑定到 Angular4 中的下拉菜单

    我正在 Angular4 应用程序中工作 我需要绑定 API 响应数据的下拉列表 我不知道如何从 API 获取特定部分的特定数据 这里API包含类别 组和子组数据 每个组有2个子组 我为此创建了一个 stackblitz 文件 请看一下 h
  • 为什么不使用 document.write 呢? [复制]

    这个问题在这里已经有答案了 为什么通常不认为使用document write在 JavaScript 中 我知道这不是最优雅或最好的方法 但是它有任何真正的错误吗 一直好吗 在什么情况下会比inner HTML 据我所知 如果在加载后使用
  • Flask 说“没有提供 FLASK_APP 环境变量”[重复]

    这个问题在这里已经有答案了 我正在尝试运行 Flask 应用程序flask run但无论如何 我收到此错误 Error Could not locate Flask application You did not provide the F
  • 如何使用 serde_json 动态构建 json 数组或对象?

    我需要在运行时构建一个 json 对象 现在 只是一个简单的 key stringvalue 对象 但每个键 值对必须添加到循环中 这看起来非常简单 基本 但我没有找到任何好的示例或文档 我终于设法让一些东西发挥作用 但它似乎太复杂了 不是
  • 如何在 Windows 上获得 git 分支的图形表示,真正显示存储库的拓扑?

    我的最后一个问题已关闭 因为显然是重复的并且已得到多次答复 然而 没有一个答案提到 无法在终端中显示 Git 树 https stackoverflow com questions 1064361 unable to show a git
  • 自定义 uitableViewCell 内的 NSTimer

    我正在从 viewController 激活自定义单元类中的函数 自定义单元格类如下所示 import UIKit class TableViewCell UITableViewCell var counter 10 class func
  • 如何对非 ASCII 字符使用 tolower 函数

    我正在尝试对非 ASCII 字符应用较低的函数 以下代码在Linux Ubuntu 环境中不起作用 但在Windows中有效 int main std string data std transform data begin data en
  • Java:来自 class.getResource( ... ) 的 NullPointerException

    我正在编写一个小型应用程序 当我尝试创建 ImageIcon 时 我总是遇到异常 异常是由这行代码引起的 prayerLevel setIcon new ImageIcon getClass getResource icons icon p
  • 使用原始文件的共享外部包?

    我们有一些项目依赖共享类型进行通信 团队决定使用这些类型的共享包 并希望使用协议缓冲区来实现此实现 如果重要的话 语言是 Go 并且所有这些项目都不是公开的 我们如何使用项目中未定义的协议缓冲区类型 我似乎无法找到一种在应用程序之间共享完全
  • 在 C 扩展中定义 Python 枚举 - 我这样做对吗?

    我正在开发一个 Python C 扩展 我想公开一个完全在 C 中定义的自定义枚举 如 继承自 enum Enum 的类 事实证明这不是一个简单的任务 并且使用常规的继承机制 tp base不起作用 很可能是由于 Enum 的元类没有被引入