如何在 python 中定义 C 枚举类型

How to define C-Enumeration types in python

我在 C 中有一个枚举数据类型。我应该如何在 python-ctypes 中声明它?我希望这个枚举变量成为结构的一部分,并且将值分配给这个结构将通过 memmove 完成。分配后,我想显示结构中每个变量的值,对于枚举类型,我想显示枚举字符串。

The Enumeration class suggested by Raj Kumar 被破坏是因为它要求 __init__ 是 运行 在变量中设置一个新值,因此如果在 C 端更改值则无法使用。这是其固定版本:

class EnumerationType(type(c_uint)):
    def __new__(metacls, name, bases, dict):
        if not "_members_" in dict:
            _members_ = {}
            for key, value in dict.items():
                if not key.startswith("_"):
                    _members_[key] = value

            dict["_members_"] = _members_
        else:
            _members_ = dict["_members_"]

        dict["_reverse_map_"] = { v: k for k, v in _members_.items() }
        cls = type(c_uint).__new__(metacls, name, bases, dict)
        for key,value in cls._members_.items():
            globals()[key] = value
        return cls

    def __repr__(self):
        return "<Enumeration %s>" % self.__name__

class CEnumeration(c_uint):
    __metaclass__ = EnumerationType
    _members_     = {}

    def __repr__(self):
        value = self.value
        return "<%s.%s: %d>" % (
            self.__class__.__name__,
            self._reverse_map_.get(value, '(unknown)'),
            value
        )

    def __eq__(self, other):
        if isinstance(other, (int, long)):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

现在可以声明一个 CEnumeration:

class EBoolean(CEnumeration):
    FALSE = 0
    TRUE = 1

并使用它:

class HeaderStruct(Structure):
    _fields_ = [("param1", EBoolean), 
                ("param2", c_uint)]

示例:

>>> header = HeaderStruct()
>>> header.param1
<EBoolean.FALSE: 0>
>>> memmove(addressof(header), b'\x01', 1)  # write LSB 0x01 in the boolean
>>> header.param1
<EBoolean.TRUE: 1>
>>> header.param1 == EBoolean.TRUE
True
>>> header.param1 == 1   # as a special case compare against ints
True
>>> header.param1.value
1L

Antti Haapala 的回答非常出色!然而,我 运行 在与 Python 3.2.2 一起使用时遇到了一些我认为值得注意的小问题。而不是:

class CEnumeration(c_uint):
    __metaclass__ = EnumerationType
    _members_     = {}

你需要做的:

class CEnumeration(c_uint, metaclass = EnumerationType):
    _members_     = {}

另外,int和long在Python3中统一了,所以:

def __eq__(self, other):
        if isinstance(other, (int, long)):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

变为:

def __eq__(self, other):
        if isinstance(other, int):
            return self.value == other

        return type(self) == type(other) and self.value == other.value

这是 Antti Happala 的解决方案的扩展,使用 Tigger 建议的 Python 3 的修改,加上任意 ctypes 的扩展作为基础 class(例如 uint8 与 uint16 ):

from ctypes import *


def TypedEnumerationType(tp):
    class EnumerationType(type(tp)):  # type: ignore
        def __new__(metacls, name, bases, dict):
            if not "_members_" in dict:
                _members_ = {}
                for key, value in dict.items():
                    if not key.startswith("_"):
                        _members_[key] = value

                dict["_members_"] = _members_
            else:
                _members_ = dict["_members_"]

            dict["_reverse_map_"] = {v: k for k, v in _members_.items()}
            cls = type(tp).__new__(metacls, name, bases, dict)
            for key, value in cls._members_.items():
                globals()[key] = value
            return cls

        def __repr__(self):
            return "<Enumeration %s>" % self.__name__

    return EnumerationType


def TypedCEnumeration(tp):
    class CEnumeration(tp, metaclass=TypedEnumerationType(tp)):
        _members_ = {}

        def __repr__(self):
            value = self.value
            return f"<{self.__class__.__name__}.{self._reverse_map_.get(value, '(unknown)')}: {value}>"

        def __eq__(self, other):
            if isinstance(other, int):
                return self.value == other

            return type(self) == type(other) and self.value == other.value

    return CEnumeration

这是对此的一个小单元测试,表明它确实可以区分 unit8 和 uint16 枚举:

class Foo(TypedCEnumeration(c_uint16)):
        A = 42
        B = 1337

    class Bar(TypedCEnumeration(c_uint8)):
        A = 5
        B = 23

    assert isinstance(Foo(Foo.A), c_uint16)
    assert isinstance(Bar(Bar.A), c_uint8)

    assert type(Foo.A) == int
    assert Foo.A == 42
    assert str(Foo(Foo.A)) == "<Foo.A: 42>"
    assert str(Bar(Bar.B)) == "<Bar.B: 23>"

    class FooBar(Structure):
        _pack_ = 1
        _fields_ = [("foo", Foo), ("bar", Bar)]

    foobar = FooBar(Foo.A, Bar.B)

    assert sizeof(foobar) == 3
    assert foobar.foo.value == 42
    assert foobar.bar.value == 23

    assert [int(x) for x in bytes(foobar)] == [42, 0, 23]