如何在 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]
我在 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]