【问题标题】:Extend ctypes to specify field overloading扩展 ctypes 以指定字段重载
【发布时间】:2018-01-13 15:22:07
【问题描述】:

我想扩展 ctypes Structure、BigEndianStructure、LittleEndianStructure。

能够为每个字段指定描述,并重载如何将变量返回到可能的枚举、polyco 等属性。

以下是我想做的事情,但不确定如何制作 ModifedCTypesStructure 父类。

我的目标是用它来指挥/遥测二进制数据。

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

【问题讨论】:

    标签: python metaprogramming ctypes metaclass telemetry


    【解决方案1】:

    有可能—— ctypes.Structure 提供的大部分魔法是由于它的字段是“描述符” - 即 Python's descriptor protocol 之后的对象 - 类似于我们在类主体中使用 @property 装饰器时得到的结果。

    ctypes.Structure 有一个元类,负责将特殊大小写变量名称 _fields_ 中列出的每个字段转换为 _ctypes.CField 对象(您可以通过在交互式 Python 提示符中验证 type(mystryct.field) 的结果来检查它.

    因此,为了扩展字段本身的行为,我们需要扩展这个CField 类 - 并修改创建您的结构的元类以使用我们的字段。 CField 类本身似乎是一个普通的 Python 类 - 所以如果我们尊重对超级方法的调用,它很容易修改。

    然而在你的“愿望清单”中有一些收获:

    1. 使用“帮助”要求 Python 对象将帮助字符串嵌入其类 __doc__ 属性(而不是实例)中。因此,每次从结构类中检索字段本身时,我们都可以这样做,我们会在所需的帮助下动态地创建一个新类。

    2. 当从一个对象中检索一个值时,Python 不能提前“知道”这个值是仅仅被repr“查看”还是被实际使用。因此,我们要么为具有自定义表示的值自定义 a.fieldB 返回的值,要么根本不这样做。下面的代码确实创建了一个具有自定义表示的字段检索动态类,并尝试保留基础值的所有其他数字属性。但这设置得既慢又可能出现一些不兼容问题——你可以选择在不调试值时关闭它,或者只是获取原始值。

    3. Ctype 的“字段”当然会有一些自己的内部结构,例如每个内存位置的偏移量等等 - 因此,我建议采用以下方法:(1) 创建一个新的“字段” " 完全不从 ctypes.Field 继承的类 - 并且实现了您想要的增强功能; (2) 在创建 ModifiedStructure 时,创建所有以“_”为前缀的名称,并将这些名称传递给原始 Ctypes.Structure 元类以像往常一样创建其字段; (3) 让我们的“Field”类对原来的 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
    

    【讨论】:

    • 如果您要同时继承 BigEndianStructure 和 LittleEndianStructure,您会制作一个 ModifiedStructureMetaLE 和一个 ModifiedStructureMetaBE 吗?我试图通读 ctypes 代码,它全部用 C 语言编写。
    • 您将需要再创建一个元类 - 只需检查 type (ctypes.LittleEndianStructure) 的返回值,BigEndian 也是如此 - 您将看到有两个单独的元类。我不需要更改代码,只需更改自定义元类继承的元类就足够了。不过,我创建的“Field”类应该适用于所有三个。
    • 你将如何处理位域?
    【解决方案2】:

    这是我在玩了一段时间元类之后想出的。我从来没有使用过它们,所以我不确定这是否是正确的方法。

    我无法弄清楚 repr 的事情。我要玩弄你的解决方案@jsbueno。

    我最终在创建类时构建并附加属性。 我读到的所有内容都在说 99% 的时间不要使用元类,所以有点怀疑我是否走错了路。

    我还想为 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()
    

    【讨论】:

      猜你喜欢
      • 2021-08-11
      • 2011-12-25
      • 2011-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多