Metaprogramming using metaclasses in Python
We all have heard a lot about basic concepts classes in Python like their syntax, their attributes, how to create objects, but there are more interesting things about classes in Python.
First of all, in Python everything is an object: we got integers, strings, functions, even the classes are objects. Yes, even them. So we can treat them like any other object, like pass it as a function parameter:
class C: ...
def f(c: type) -> C:
return c()
obj = f(C) # instance of the class C
or return them:
class C: ...
def f() -> type:
return C
c = f() # class C
obj = c() # instance of class C
or assign and print them:
class C: ...
c = C
print(c) # <class '__main__.C'>
We can also create classes dynamically, using the function type by passing the name of the class, the classes to inherit and the attributes with their values:
def f(self):
return 2
name = "C"
bases = ()
attrs = {"attr": 1, "f": f}
C = type(name, bases, attrs) # create the class C using the type function
obj = C() # instance of class C
print(obj.attr) # 1
print(obj.f()) # 2
So, if the classes are objects, how do we call the class of a class? We call it metaclass. A metaclass defines how a class is created. We can use metaclasses to add attributes or functionalities to a class, verify class definitions, track declared classes, and more.
领英推è
The metaclass type is the default metaclass of every class, and to create a custom metaclass we have to inherit from it:
class MC(type): ... # metaclass
class C(metaclass=MC): ... # class, its metaclass is MC
obj = C() # object of the class C
So, what is metaprogramming? Metaprogramming is a technique in which code can read, generate, modify, or transform other code. This includes advanced features like declaring metaclasses. Using metaclasses, we can verify if a class implements a method:
class MC(type):
def __init__(cls, name, bases, attrs):
if "f" not in attrs:
raise TypeError(f"Class {name} must define a 'f' method")
super().__init__(name, bases, attrs)
# will not raise an error
class C1(metaclass=MC):
def f(): ...
# will raise an error
class C2(metaclass=MC): ...
or add a default value to an attribute:
class MC(type):
def __init__(cls, name, bases, attrs):
if "attr" not in attrs:
cls.attr = "default"
super().__init__(name, bases, attrs)
class C(metaclass=MC): ...
print(C.attr) # default
or write beautiful singleton solutions:
class Singleton(type):
instance = None
def __call__(cls, *args, **kwargs):
if not cls.instance:
cls.instance = super().__call__(*args, **kwargs)
return cls.instance
class C1(metaclass=Singleton): ...
In conclusion, metaclasses are a powerful feature in Python, enabling advanced customization and control over class behavior. While they can be complex, their ability to enforce rules, add attributes and patterns like singletons can lead to more maintainable and elegant code in the right scenarios.