1

我想扩展 ctypes Structure、BigEndianStructure、LittleEndianStructure。

能够为每个字段指定一个描述,并重载如何将变量返回到可能的 enum、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
4

2 回答 2

2

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

ctypes.Structure有一个元类,负责将特殊大小写变量名称中列出的每个字段转换_fields_为一个对象(您可以通过在交互式 Python 提示符中_ctypes.CField验证结果来检查它。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
于 2017-08-07T18:49:38.100 回答
1

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

我无法弄清楚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()
于 2017-08-07T19:06:31.207 回答