2

I'm trying to create a metaclass for the class I created here: ctypes variable length structures

I want to simplify the Points class so it looks like this (Python 3.2):

class Points(c.Structure, metaclass=VariableMeta):
    _fields_ = [
        ('num_points', c.c_uint32),
        ('points', 'Point*self.num_points')
    ]
    def __init__(self):
        self.num_points = 0
        self.points = [0,]*MAX_SIZE

This is the metaclass I have so far:

class VariableMeta(type):
    def __new__(cls, name, bases, dct):
        dct['_inner_fields'] = dct['_fields_']
        dct['_fields_'] = [('_buffer', c.c_byte*MAX_PACKET_SIZE)]
        return type.__new__(cls, name, bases, dct)

    def parse(self):
        fields = []
        for name, ctype in self._inner_fields:
            if type(ctype) == str:
                ctype = eval(ctype)
            fields.append((name, ctype))
            class Inner(c.Structure, PrettyPrinter):
                _fields_ = fields
            inner = Inner.from_address(c.addressof(self._buffer))
            setattr(self, name, getattr(inner, name))
        self = inner
        return self

    def pack(self):
        fields = []
        for name, ctype in self._inner_fields:
            if type(ctype) == str:
                ctype = eval(ctype)
            fields.append((name, ctype))
        class Inner(c.Structure, PrettyPrinter):
            _fields_ = fields
        inner = Inner()
        for name, ctype in self._inner_fields:
            value = getattr(self, name)
            if type(value) == list:
                l = getattr(inner, name)
                for i in range(len(l)):
                    l[i] = getattr(self, name)[i]
            else:
                setattr(inner, name, value)
        return inner

It looks like it should work, but when I run it I get the error: TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases.

I searched for hints to the solution of this problem, but ctypes Structure looks to be implemented in a c library. I am not sure how to fix this, any help or the specific solution is appreciated!

Community
  • 1
  • 1
Jake
  • 2,515
  • 5
  • 26
  • 41

1 Answers1

5

The problem is that ctypes.Structure uses its own custom metaclass: _ctypes.StructType. Since you inherit the metaclass from Structure, Python does not know which metaclass to use when constructing your class.

You can fix this by inheriting your metaclass from _ctypes.StructType. Since the name of the metaclass is an implementation detail of the ctypes module, I recommend writing type(ctypes.Structure) to get the metaclass dynamically.

import ctypes

class VariableMeta(type(ctypes.Structure)):
    pass

The drawback with this approach is that you limit the use of your metaclass. This might be OK if you only plan to use it for subclasses of ctypes.Structure.

Another approach is to create a intermediate metaclass that inherits from both metaclasses.

class PointsMetaClass(type(ctypes.Structure), VariableMeta):
    pass

class Points(c.Structure, metaclass=PointsMetaClass):
    # ...

Always make sure that you use super() instead of hard-coding type in your metaclass' __new__!

return super(VariableMeta, cls).__new__(cls, name, bases, dct)

As Guido once wrote: Writing metaclasses in Python will cause your head to explode!

Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145