扩展 ctypes 以指定字段重载
Extend ctypes to specify field overloading
我想扩展 ctypes 结构、BigEndianStructure、LittleEndianStructure。
指定每个字段都有描述的能力,以及如何重载变量返回到可能的枚举、polyco 等属性的能力。
像下面这样的事情是我想做的,但我不确定如何使 ModifedCTypesStructure 成为父级 class。
我的目标是将其用于二进制数据的命令/遥测。
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Packet(ModifedCTypesStructure):
__fields__ = [("fieldA",ctypes.c_int32,
{"brief":"""Description of fieldA""",
"enum":Color}
),
("fieldB",ctypes.c_uint32,
{"repr":lambda x: hex(x)}
)
]
a = Packet()
help(a.fieldA)
> Description of fieldA
a.fieldA = Color.RED
print a._fieldA # Return the binary field
> 1
a.fieldB = 0xbb
print a.fieldB
> 0xbb #Note repr is called to return '0xbb'
print a._fieldB
> 187
有可能——
ctypes.Structure
提供的大部分魔法是由于它的字段是 "descriptors" - 即 Python's descriptor protocol 之后的 object - 类似于我们使用 @property
class body.
中的装饰器
ctypes.Structure
有一个 metaclass 负责将特殊大小写变量名称 _fields_
中列出的每个字段转换为 _ctypes.CField
object (您可以通过在交互式 Python 提示中验证 type(mystryct.field)
的结果来检查。
因此,为了扩展字段本身的行为,我们需要扩展此 CField
class - 并将创建结构的元 class 修改为使用我们的领域。 CField class 本身似乎是一个普通的 Python class - 所以很容易修改,如果我们尊重对 super-methods.
的调用
但是你的"wishlist"有一些问题:
使用 "help" 需要 Python object 将帮助字符串嵌入其 class __doc__
属性(不是实例)。因此,每次从结构 class 中检索字段本身时,我们都可以这样做,我们动态地使用所需的帮助创建一个新的 class。
从 object 中检索值时,如果该值仅用于 "viewed",则 Python 不能提前 "know" ] 由 repr
或将实际使用。因此,我们要么为具有自定义表示的值自定义 a.fieldB
返回的值,要么根本不这样做。下面的代码 确实 在字段检索上创建了一个动态的 class,它将具有自定义表示,并尝试保留基础值的所有其他数字属性。但这被设置为既慢又可能存在一些不兼容性 - 您可以选择在不调试值时将其关闭,或者只是获取原始值。
Ctype 的 "Fields" 当然会有一些自己的内部结构,比如每个内存位置的偏移量等等 - 因此,我建议采用以下方法:(1 ) 创建一个新的 "Field" class ,它根本不继承 ctypes.Field - 并且实现了你想要的增强功能; (2) 在 ModifiedStructure 创建时创建所有“_”前缀的名称,并将这些名称传递给原始 Ctypes.Structure metaclass 以像往常一样创建其字段; (3) 让我们的 "Field" class 读取和写入原始 ctypes.Fields,并拥有它们的自定义转换和表示。
如您所见,我还负责在编写时实际转换 Enum 值。
要尝试一切,只需从下面的 "ModifiedStructure" 继承,而不是 ctypes.Structure
:
from ctypes import Structure
import ctypes
class A(Structure):
_fields_ = [("a", ctypes.c_uint8)]
FieldType = type(A.a)
StructureType = type(A)
del A
def repr_wrapper(value, transform):
class ReprWrapper(type(value)):
def __new__(cls, value):
return super().__new__(cls, value)
def __repr__(self):
return transform(self)
return ReprWrapper(value)
def help_wrapper(field):
class Field2(field.__class__):
__doc__ = field.help
def __repr__(self):
return self.__doc__
return Field2(field.name, field.type_, help=field.help, repr=field.repr, enum=field.enum)
class Field:
def __init__(self, name, type_, **kwargs):
self.name = name
self.type_ = type_
self.real_name = "_" + name
self.help = kwargs.pop("brief", f"Proxy structure field {name}")
self.repr = kwargs.pop("repr", None)
self.enum = kwargs.pop("enum", None)
if self.enum:
self.rev_enum = {constant.value:constant for constant in self.enum.__members__.values() }
def __get__(self, instance, owner):
if not instance:
return help_wrapper(self)
value = getattr(instance, self.real_name)
if self.enum:
return self.rev_enum[value]
if self.repr:
return repr_wrapper(value, self.repr)
return value
def __set__(self, instance, value):
if self.enum:
value = getattr(self.enum, value.name).value
setattr(instance, self.real_name, value)
class ModifiedStructureMeta(StructureType):
def __new__(metacls, name, bases, namespace):
_fields = namespace.get("_fields_", "")
classic_fields = []
for field in _fields:
# Create the set of descriptors for the new-style fields:
name = field[0]
namespace[name] = Field(name, field[1], **(field[2] if len(field) > 2 else {}))
classic_fields.append(("_" + name, field[1]))
namespace["_fields_"] = classic_fields
return super().__new__(metacls, name, bases, namespace)
class ModifiedStructure(ctypes.Structure, metaclass=ModifiedStructureMeta):
__slots__ = ()
并在交互式提示上测试它:
In [165]: class A(ModifiedStructure):
...: _fields_ = [("b", ctypes.c_uint8, {"enum": Color, 'brief': "a color", }), ("c", ctypes.c_uint8, {"repr": hex})]
...:
...:
In [166]: a = A()
In [167]: a.c = 20
In [169]: a.c
Out[169]: 0x14
In [170]: a.c = 256
In [171]: a.c
Out[171]: 0x0
In [172]: a.c = 255
In [173]: a.c
Out[173]: 0xff
In [177]: a.b = Color.RED
In [178]: a._b
Out[178]: 1
In [180]: help(A.b)
(shows full Field class help starting with the given description)
In [181]: A.b
Out[181]: a color
这是我在使用 meta类 一段时间后想到的。我从未使用过它们,所以我不确定这是否是正确的方法。
我无法弄清楚 repr 的事情。我将试用您的解决方案@jsbueno。
我最终在创建时构建并将属性附加到 类。
我读到的所有内容都在说 99% 的时间不要使用 meta类 所以有点想知道我是否走错了路。
我还想为 BigEndian / LittleEndian 结构使用相同的元类,忘记将其添加到愿望清单中。
import ctypes
def make_fget(key,enum=None):
def fget(self):
res = getattr(self, key)
if enum != None:
res = enum(res)
return res
return fget
def make_fset(key):
def fset(self, value):
if isinstance(value,Enum):
value = value.value
setattr(self, key, value)
return fset
class PyGndTlmMeta(type):
def __new__(cls,name,bases, dct):
endian = ctypes.Structure
if '_endian_' in dct:
endian = dct['_endian_']
if not endian in [ctypes.Structure, ctypes.LittleEndianStructure, ctypes.BigEndianStructure]:
pass #TODO throw error
fields = []
attributes = dct.copy()
for f in dct['_fields_']:
fname = '_' + f[0]
ftype = f[1]
res = (fname,ftype)
specs = {}
#If 3rd argument is an integer than we are dealing with bitfields
if len(f) >= 3 and type(f[2]) == int:
res = (fname,ftype,f[2])
elif len(f) >= 3 and type(f[2]) == dict:
specs = f[2]
elif len(f) >= 4 and type(f[3]) == dict:
specs = f[3]
fields.append(res)
enum = None
if "enum" in specs:
enum = specs['enum']
fget = make_fget(fname,enum=enum)
fset = make_fset(fname)
doc = "Not Set"
if "brief" in specs:
doc = specs["brief"]
if "enum" in specs:
#TODO use different getter/setter
pass
attributes[f[0]] = property(fget, fset,doc=doc)
attributes['_fields_'] = fields
bases = (endian,)
x = type(name, bases, attributes)
return x
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Packet( ):
__metaclass__ = PyGndTlmMeta
_endian_ = ctypes.BigEndianStructure
_fields_ = [
("a",ctypes.c_byte,{"brief":"testing"}),
("b",ctypes.c_int, {"enum":Color})
]
x = Packet()
我想扩展 ctypes 结构、BigEndianStructure、LittleEndianStructure。
指定每个字段都有描述的能力,以及如何重载变量返回到可能的枚举、polyco 等属性的能力。
像下面这样的事情是我想做的,但我不确定如何使 ModifedCTypesStructure 成为父级 class。
我的目标是将其用于二进制数据的命令/遥测。
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Packet(ModifedCTypesStructure):
__fields__ = [("fieldA",ctypes.c_int32,
{"brief":"""Description of fieldA""",
"enum":Color}
),
("fieldB",ctypes.c_uint32,
{"repr":lambda x: hex(x)}
)
]
a = Packet()
help(a.fieldA)
> Description of fieldA
a.fieldA = Color.RED
print a._fieldA # Return the binary field
> 1
a.fieldB = 0xbb
print a.fieldB
> 0xbb #Note repr is called to return '0xbb'
print a._fieldB
> 187
有可能——
ctypes.Structure
提供的大部分魔法是由于它的字段是 "descriptors" - 即 Python's descriptor protocol 之后的 object - 类似于我们使用 @property
class body.
ctypes.Structure
有一个 metaclass 负责将特殊大小写变量名称 _fields_
中列出的每个字段转换为 _ctypes.CField
object (您可以通过在交互式 Python 提示中验证 type(mystryct.field)
的结果来检查。
因此,为了扩展字段本身的行为,我们需要扩展此 CField
class - 并将创建结构的元 class 修改为使用我们的领域。 CField class 本身似乎是一个普通的 Python class - 所以很容易修改,如果我们尊重对 super-methods.
但是你的"wishlist"有一些问题:
使用 "help" 需要 Python object 将帮助字符串嵌入其 class
__doc__
属性(不是实例)。因此,每次从结构 class 中检索字段本身时,我们都可以这样做,我们动态地使用所需的帮助创建一个新的 class。从 object 中检索值时,如果该值仅用于 "viewed",则 Python 不能提前 "know" ] 由
repr
或将实际使用。因此,我们要么为具有自定义表示的值自定义a.fieldB
返回的值,要么根本不这样做。下面的代码 确实 在字段检索上创建了一个动态的 class,它将具有自定义表示,并尝试保留基础值的所有其他数字属性。但这被设置为既慢又可能存在一些不兼容性 - 您可以选择在不调试值时将其关闭,或者只是获取原始值。Ctype 的 "Fields" 当然会有一些自己的内部结构,比如每个内存位置的偏移量等等 - 因此,我建议采用以下方法:(1 ) 创建一个新的 "Field" class ,它根本不继承 ctypes.Field - 并且实现了你想要的增强功能; (2) 在 ModifiedStructure 创建时创建所有“_”前缀的名称,并将这些名称传递给原始 Ctypes.Structure metaclass 以像往常一样创建其字段; (3) 让我们的 "Field" class 读取和写入原始 ctypes.Fields,并拥有它们的自定义转换和表示。
如您所见,我还负责在编写时实际转换 Enum 值。
要尝试一切,只需从下面的 "ModifiedStructure" 继承,而不是 ctypes.Structure
:
from ctypes import Structure
import ctypes
class A(Structure):
_fields_ = [("a", ctypes.c_uint8)]
FieldType = type(A.a)
StructureType = type(A)
del A
def repr_wrapper(value, transform):
class ReprWrapper(type(value)):
def __new__(cls, value):
return super().__new__(cls, value)
def __repr__(self):
return transform(self)
return ReprWrapper(value)
def help_wrapper(field):
class Field2(field.__class__):
__doc__ = field.help
def __repr__(self):
return self.__doc__
return Field2(field.name, field.type_, help=field.help, repr=field.repr, enum=field.enum)
class Field:
def __init__(self, name, type_, **kwargs):
self.name = name
self.type_ = type_
self.real_name = "_" + name
self.help = kwargs.pop("brief", f"Proxy structure field {name}")
self.repr = kwargs.pop("repr", None)
self.enum = kwargs.pop("enum", None)
if self.enum:
self.rev_enum = {constant.value:constant for constant in self.enum.__members__.values() }
def __get__(self, instance, owner):
if not instance:
return help_wrapper(self)
value = getattr(instance, self.real_name)
if self.enum:
return self.rev_enum[value]
if self.repr:
return repr_wrapper(value, self.repr)
return value
def __set__(self, instance, value):
if self.enum:
value = getattr(self.enum, value.name).value
setattr(instance, self.real_name, value)
class ModifiedStructureMeta(StructureType):
def __new__(metacls, name, bases, namespace):
_fields = namespace.get("_fields_", "")
classic_fields = []
for field in _fields:
# Create the set of descriptors for the new-style fields:
name = field[0]
namespace[name] = Field(name, field[1], **(field[2] if len(field) > 2 else {}))
classic_fields.append(("_" + name, field[1]))
namespace["_fields_"] = classic_fields
return super().__new__(metacls, name, bases, namespace)
class ModifiedStructure(ctypes.Structure, metaclass=ModifiedStructureMeta):
__slots__ = ()
并在交互式提示上测试它:
In [165]: class A(ModifiedStructure):
...: _fields_ = [("b", ctypes.c_uint8, {"enum": Color, 'brief': "a color", }), ("c", ctypes.c_uint8, {"repr": hex})]
...:
...:
In [166]: a = A()
In [167]: a.c = 20
In [169]: a.c
Out[169]: 0x14
In [170]: a.c = 256
In [171]: a.c
Out[171]: 0x0
In [172]: a.c = 255
In [173]: a.c
Out[173]: 0xff
In [177]: a.b = Color.RED
In [178]: a._b
Out[178]: 1
In [180]: help(A.b)
(shows full Field class help starting with the given description)
In [181]: A.b
Out[181]: a color
这是我在使用 meta类 一段时间后想到的。我从未使用过它们,所以我不确定这是否是正确的方法。
我无法弄清楚 repr 的事情。我将试用您的解决方案@jsbueno。
我最终在创建时构建并将属性附加到 类。 我读到的所有内容都在说 99% 的时间不要使用 meta类 所以有点想知道我是否走错了路。
我还想为 BigEndian / LittleEndian 结构使用相同的元类,忘记将其添加到愿望清单中。
import ctypes
def make_fget(key,enum=None):
def fget(self):
res = getattr(self, key)
if enum != None:
res = enum(res)
return res
return fget
def make_fset(key):
def fset(self, value):
if isinstance(value,Enum):
value = value.value
setattr(self, key, value)
return fset
class PyGndTlmMeta(type):
def __new__(cls,name,bases, dct):
endian = ctypes.Structure
if '_endian_' in dct:
endian = dct['_endian_']
if not endian in [ctypes.Structure, ctypes.LittleEndianStructure, ctypes.BigEndianStructure]:
pass #TODO throw error
fields = []
attributes = dct.copy()
for f in dct['_fields_']:
fname = '_' + f[0]
ftype = f[1]
res = (fname,ftype)
specs = {}
#If 3rd argument is an integer than we are dealing with bitfields
if len(f) >= 3 and type(f[2]) == int:
res = (fname,ftype,f[2])
elif len(f) >= 3 and type(f[2]) == dict:
specs = f[2]
elif len(f) >= 4 and type(f[3]) == dict:
specs = f[3]
fields.append(res)
enum = None
if "enum" in specs:
enum = specs['enum']
fget = make_fget(fname,enum=enum)
fset = make_fset(fname)
doc = "Not Set"
if "brief" in specs:
doc = specs["brief"]
if "enum" in specs:
#TODO use different getter/setter
pass
attributes[f[0]] = property(fget, fset,doc=doc)
attributes['_fields_'] = fields
bases = (endian,)
x = type(name, bases, attributes)
return x
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
class Packet( ):
__metaclass__ = PyGndTlmMeta
_endian_ = ctypes.BigEndianStructure
_fields_ = [
("a",ctypes.c_byte,{"brief":"testing"}),
("b",ctypes.c_int, {"enum":Color})
]
x = Packet()