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

5

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


1
你具体是指哪个类? - Makoto
是的,没错。https://docs.python.org/2/library/ctypes.html 上次我检查时,addressof 不是 C 语言 ;) - deets
"header" 是 Ctypes-Structure 的一个实例。我想知道当我尝试通过 memmove 分配 "header" 实例的变量时,它所属的类的哪个方法将被调用。从 memmove 这个术语中,我可以猜测它必须是一个内存复制,并且它可能甚至不知道这个复制正在进行的位置。因此,Ctypes-Structure 类的任何方法都不会被调用。这正确吗? - Raj Kumar
你的意思是,我不能使用那个吗? - Raj Kumar
1
@RajKumar:现在你已经清楚你面临的实际问题,你可能想要[编辑]你的问题。 - Martijn Pieters
显示剩余4条评论
3个回答

10

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

嗨,感谢您的回答。但是,您能否再解释一下吗?目前,分配该变量的唯一方法是通过memmove。在执行此操作后,我可以使用getattr()读取结构中其他“c_uint”变量分配的值。我不确定要调用此对象中的哪种方法才能获取ctypes结构中分配给该变量的值。 - Raj Kumar
非常感谢!这正是我想要的。再次感谢您花费时间来纠正它 :-) - Raj Kumar
以上评论并不是特别针对你的。我已经为你的答案点赞了。我希望3.x版本的枚举模块能够更容易地实现这一点,但是enum.EnumMeta会导致元类冲突。 - Eryk Sun
只是需要注意的一个小问题,globals()与定义CEnumeration子类(例如EBoolean)的模块无关,而是与定义EnumerationType的模块有关... 所有子类中的所有条目定义都将在那里定义。 - Tcll
请考虑使用 sys._getframe(1).f_globals.update(_members_) (并记得删除帧), 或使用 inspect 进行更加 pythonic 的方法。 - Tcll
显示剩余2条评论

3

安蒂·哈帕拉(Antti Haapala)非常出色地回答了问题!然而,我在使用Python 3.2.2时遇到了一些小问题,我认为值得注意。不是:

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

你需要做的事情是:
class CEnumeration(c_uint, metaclass = EnumerationType):
    _members_     = {}

此外,在Python 3中,int和long已经被统一起来了,因此:

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

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

Becomes:

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

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

2
这里是Antti Happala的解决方案的扩展,使用Tigger建议的Python 3修改,并添加了任意ctypes作为基类的扩展(例如uint8 vs. 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]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接