Arjun

Python Custom Metaclass

🗓️ May 03, 2020 • ⌛ 2 min read

As discussed in the metaclasses definition, whenever an object is created for a class, the __call__ method of a metaclass is called which in turn calls the __new__ and then __init__ method of the class for which the object is created.

class Foo:
  pass

f = Foo()

Here when f is created, the metaclass of Foo which is type calls the __new__ method in Foo. But since Foo doesn’t implement the method __new__ , it tries to invoke the method from the Foo’s ancestor which in this case would be object.

With that, if you want to override the object instantiation for any object, just implement __new__ in the object as below.

class Foobar:
    def __init__(self):
      self.var = "Hello World!"

    def __new__(cls):
      print("Creating instance")
      return super().__new__(cls)

x = Foobar() // Creating instance
print(x.var) // Hello World!

In the same way, since the class is an object of type metaclass, the instantiation of the class can be customized by overriding __new__ in the metaclass.

def new(cls):
  x = type.__new__(cls)
  x.val = "Hello World!"
  return x // Here x is a class object

type.__new__ = new // TypeError: can't set attributes of built-in/extension type 'type' 

class Foo:
  pass

Though the above code makes sense, python won’t allow setting attributes to type class directly and even if it does, it will end up an infinite loop. So to overcome this, we need to create a custom metaclass to achieve what we want. Custom metaclasses are user-defined metaclasses which are derived from type metaclass. So let’s define a custom metaclass first.

class CustomMeta(type):
  def __new__(cls, name, bases, dict):
    x = super().__new__(cls, name, bases, dict)
    x.val = "Hello World!"
    return x

The above metaclass just overrides __new__ method and creates the intended class object whenever class code is executed. Now let’s see how to use this custom metaclass.

class Foo(metaclass=CustomMeta):
  pass

f = Foo()
print(f.val) // Hello World!
print(Foo.val) // Hello World!

Here we have defined a class and specified it’s metaclass using the metaclass keyword. Now all the classes with this custom metaclass will have attribute val assigned to it automatically. We can use it as a class factory.


The central enemy of reliability is complexity