import struct def preargs(cls): def _pre_init(*args1, **kwargs1): def _my_init(*args2, **kwargs2): args = args1 + args2 kwargs1.update(kwargs2) return cls(*args, **kwargs1) return _my_init return _pre_init class BinaryMetaType(type): def __getitem__(self, val): return array(self, val) class BinaryType(metaclass=BinaryMetaType): def __init__(self, **kwargs): self._kwargs = kwargs def to_binary(self, val): pass def from_binary(self, binary): pass class SimpleBinaryType(BinaryType): def __init__(self, fmt): self._struct = struct.Struct(fmt) def to_binary(self, val): return self._struct.pack(val) def from_binary(self, binary): return (self._struct.size, self._struct.unpack(binary[:self._struct.size])[0]) @preargs class array(BinaryType): def __init__(self, arrtype, arrlen, **kwargs): super().__init__(**kwargs) self._arrtype, self._arrlen = arrtype(**kwargs), arrlen def to_binary(self, val): res = [] for i,v in enumerate(val): res.append(self._arrtype.to_binary(v)) if i+1 == self._arrlen: break return b''.join(res) def from_binary(self, binary): res = [] ssum = 0 for i in range(self._arrlen): s,v = self._arrtype.from_binary(binary[ssum:]) ssum += s res.append(v) return ssum, res class dword(SimpleBinaryType): def __init__(self, **kwargs): super().__init__('I', **kwargs) class char(SimpleBinaryType): def __init__(self, **kwargs): super().__init__('c', **kwargs) class BinaryBuilder(dict): def __init__(self, **kwargs): self.members = [] self._kwargs = kwargs def __setitem__(self, key, value): if key == '__module__': return if key not in self: self.members.append((key, value(**self._kwargs))) super().__setitem__(key, value) class Binary(type): @classmethod def __prepare__(*bases, **kwargs): # In the future kwargs can contain things such as endianity # and alignment return BinaryBuilder(**kwargs) def __new__(cls, name, bases, classdict): # There are nicer ways of doing this, but as a hack it works def fixupdict(d): @classmethod def to_binary(clas, datadict): res = [] for k,v in clas.members: res.append(v.to_binary(datadict[k])) return b''.join(res) @classmethod def from_binary(cls, bytesin): res = {} ssum = 0 for k,v in cls.members: i, d = v.from_binary(bytesin[ssum:]) ssum += i res[k] = d return ssum, res nd = {'to_binary': to_binary, 'from_binary': from_binary, 'members': d.members} return nd return super().__new__(cls, name, bases, fixupdict(classdict)) #### How one would use the above module class BMP(metaclass=Binary): # The point was to try and get this C-like syntax bfType = char[2] bfSize = dword bfReserved = dword bfOffBits = dword print(BMP.from_binary(b'BM6\x00$\x00\x00\x00\x00\x006\x00\x00\x00')) print(BMP.to_binary( {'bfType': 'BM', 'bfSize': 2359350, 'bfReserved': 0, 'bfOffBits': 54}))