Python ctypes bitfields
ctypes, python
Solution
You can achieve what you describe by creating a Register metaclass that extends `type(ctypes.Union)` as below:
class Register(type(ctypes.Union)):
def __new__(mcs, name, bases, dict):
#make the bitfield class
class regBitfield(ctypes.LittleEndianStructure):
_fields_ = dict['_fields_']
#set up the Union
class regUnion(ctypes.Union):
_fields_ = [('base', dict['_basetype_']), ('_b', regBitfield)]
_anonymous_ = ('_b', )
__iter__ = _register_iter
keys = _register_keys
items = _register_items
update = _register_update
#return a subclass of the temporary regUnion class
return type(ctypes.Union).__new__(mcs, name, (regUnion,), dict)
class IEEE754: #for Python3 use class IEEE754(metaclass=Register):
__metaclass__ = Register #omit this line Python3
_fields_ = [('mantissa', ctypes.c_uint, 23),
('exponent', ctypes.c_uint, 8),
('sign', ctypes.c_uint, 1)
]
_basetype_ = ctypes.c_float
This works in almost the same way as your `make_register` function, but utilizes the existing machinery of the class system and the special rules for extending `ctypes.Union` and `ctypes.Structure`. Specifically, any subclass of a subclass of `ctypes.Union` or `ctypes.Structure` will extend the `_fields_` list, rather than replace it. Knowing this, we can create temporary classes within the `__new__` of our `Register` metaclass that the ultimate class extends. With the above:
>>> myFloat = IEEE754(42.0)
>>> print(myFloat.base)
42.0
>>> print(myFloat.mantissa)
2621440L
>>> fltReg._fields_
[('mantissa', <class 'ctypes.c_ulong'>, 23), ('exponent', <class 'ctypes.c_ulong'>, 8), ('sign', <class 'ctypes.c_ulong'>, 1)]
The `Register.__new__` method might break if the type being created has multiple bases.
Problem
I need a bitfield definition that's compatible with ctypes Structures, so that I can use it as a pointer to a memory-mapped set of hardware registers, i.e. ``` class RegFile(Structure): _fields_ = [ ('ticks', c_uint32), ('id', id_bitfield), ] ``` Ideally they'd be very Pythonic things, and behave mostly like dicts. I've managed to work most of the gory details out to where I now have a factory function that makes bitfield classes similarly to making namedtuples. Here's an example of it ripping apart the fields of a standard float. ``` def make_register(name, fields, basetype=c_uint32): # Define the underlying bitfield type bitfield = type(name + '_bitfield', (LittleEndianStructure, ), {}) bitfield._pack_ = 1 bitfield._fields_ = fields # Set up the union d = { '_fields_' : [('base', basetype), ('_b', bitfield)], '_anonymous_' : ('_b',), '__iter__' : _register_iter, 'keys' : _register_keys, 'items' : _register_items, 'update' : _register_update } return type(name, (Union, ), d) ieee754_fields = [ ('mantissa', c_uint, 23), ('exponent', c_uint, 8), ('sign', c_uint, 1) ] IEEE754 = make_register('IEEE754', ieee754_fields, c_float) x = IEEE754() ``` This works decently, but isn't a particularly Pythonic feeling syntax. Ideally I'd have some answer that allows me to define the bitfield class as: ``` class IEEE754(Register): """Individual bitfields of a standard IEEE-754 floating point number.""" _fields_ = ieee754_fields _basetype_ = c_float ``` But I haven't been able to get that Register class to be a thing. It seemed like Register should inherit from Union and have some metaclass magic applied, but that way metaclass conflicts against Union lie. Any thoughts? Or should I just stick with what I've got?