Using metaclasses to use domain-specific protocols

Posted 4 years ago | Originally written on 9 Mar 2020

In Python (3+) it is impossible to create a class which is not a subclass of object. This means that the protocols associated with the class and instances of the class are tightly bound to object. Metaclasses provide a way to extend this so as to have additional domain-specific protocols.

The snippet below shows how this may be accomplished by overriding the type.__init__() during metaclass definition. The metaclass will have a collection of methods that will be hooked into the class once it is created. These methods may then be used to implement the domain-specific protocols.

# the metaclass
class Meta(type):
  def __init__(self, name, bases, class_dict):
    # since this is init the class has already been created
    # we introduce some arbitrary 'fire' protocol which will be invoked
    # using some 'fire()' function
    self.__fire__ = self._fire
    type.__init__(self, name, bases, class_dict)
    
  def _fire(self, *args, **kwargs):
    print('args:  ', args)
    print('kwargs: ', kwargs)


def fire(obj, *args, **kwargs):
  obj.__fire__(*args, **kwargs)
 

# now we can create classes based on this metaclass
class X(object, metaclass=Meta):
  pass
 
x = X()

# their MRO still includes object
print('X.__mro__:', X.__mro__)

# both the class and instances will support the fire protocol
print('dir(x):', dir(x))
print('dir(X):', dir(X))

# even subclasses will and their instances will support the fire protocol
class XX(X):
  pass
 
xx = XX()

print('XX.__mro__:', XX.__mro__)
print('dir(xx):', dir(xx))
print('dir(XX):', dir(XX))

# they are NOT of type 'type'
print('type(X)', type(X))
print('type(XX)', type(XX))