A metaclass in Python is a class that defines how other classes are created and behave. Just like objects are created from classes, classes themselves are created from metaclasses.
In short:
- Class → creates objects
- Metaclass → creates classes
By default, Python uses the built-in metaclass "type" to create all classes.
Steps to Create a Metaclass
- Define a metaclass: Create a class that inherits from type. Optionally, define __new__ or __init__ to customize class creation.
- Use the metaclass in a class: Specify metaclass=YourMeta when defining a class.
- Class is created by the metaclass: The metaclass can modify attributes or methods of the class automatically.
- Create instances of the class: Instances work normally, the metaclass only affects the class itself.
class Meta(type):
def __new__(cls, name, bases, dct):
dct['greet'] = lambda self: f"Hello from {name}!"
return super().__new__(cls, name, bases, dct)
class Person(metaclass=Meta):
def __init__(self, name):
self.name = name
p = Person("Olivia")
print(p.greet())
Output
Hello from Person!
Explanation:
- Meta(type): Defines a metaclass that can modify classes when they are created.
- __new__: Adds a greet method automatically to any class using this metaclass.
- Person(metaclass=Meta): Class created using the metaclass, so it gets the greet method.
- p = Person("Olivia"): Creates an instance of Person.
- p.greet(): Calls the method injected by the metaclass.
Creating Subclass
Subclasses can also be created dynamically using type.
def init(self, ftype):
self.ftype = ftype
def getFtype(self):
return self.ftype
FoodType = type('FoodType', (object,), {
'__init__': init,
'getFtype': getFtype
})
def vegFoods(self):
return {'Spinach', 'Bitter Guard'}
VegType = type('VegType', (FoodType,), {
'vegFoods': vegFoods
})
v = VegType("Vegetarian")
print(v.getFtype())
print(v.vegFoods())
Output
Vegetarian
{'Bitter Guard', 'Spinach'}
Explanation:
- VegType inherits from FoodType dynamically.
- vegFoods method is added via the attributes dictionary.
Metaclass Inheritance
Metaclasses can be inherited just like normal classes. When a class inherits from a metaclass, it becomes an instance of that metaclass.
class MetaCls(type):
pass
A = MetaCls('A', (object,), {})
class B(object):
pass
class C(A, B):
pass
print(type(A))
print(type(B))
print(type(C))
Output
<class '__main__.MetaCls'> <class 'type'> <class '__main__.MetaCls'>
Explanation:
- A is created by MetaCls -> type is MetaCls
- B is normal -> type is type
- C inherits from A -> type is MetaCls
Metaclass Conflicts
A class cannot inherit from two different metaclasses. Python will raise a TypeError.
class MetaCls(type):
pass
A = MetaCls('A', (object,), {})
print("Type of A:", type(A))
class B(object):
pass
print("Type of B:", type(B))
class C(A, B):
pass
print("Type of C:", type(C))
Error:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Explanation:
- Python allows only one metaclass per class.
- 'C' inherits from two different metaclasses → conflict occurs.
Metaclass Use Cases
Metaclasses are useful when you want to control class creation or enforce rules.
1. Class Verification: This example shows how a metaclass can enforce rules on class attributes.
class MainClass(type):
def __new__(cls, name, bases, attrs):
if 'foo' in attrs and 'bar' in attrs:
raise TypeError(f"Class {name} cannot have both foo and bar")
return super().__new__(cls, name, bases, attrs)
# This will raise an error
class SubClass(metaclass=MainClass):
foo = 42
bar = 34
Explanation:
- Ensures a class cannot have both foo and bar attributes.
- Useful for interface enforcement.
2. Prevent Subclass Inheritance: This example shows how a metaclass can treat abstract classes differently, preventing certain checks for them while enforcing rules on normal classes.
class MetaCls(type):
def __new__(cls, name, bases, attrs):
if attrs.pop('abstract', False):
print('Abstract Class:', name)
return super().__new__(cls, name, bases, attrs)
print('Normal Class:', name)
return super().__new__(cls, name, bases, attrs)
class AbsCls(metaclass=MetaCls):
abstract = True
class NormCls(metaclass=MetaCls):
foo = 42
Output
Abstract Class: AbsCls Normal Class: NormCls
Explanation:
- Abstract classes can skip metaclass checks.
- Normal classes are validated by the metaclass.
Dynamic Generation of Classes
Dynamic class generation allows you to create classes at runtime instead of defining them statically in code.
class FoodType:
events = []
def __init__(self, ftype, items):
self.ftype = ftype
self.items = items
FoodType.events.append(self)
def run(self):
print("Food Type:", self.ftype)
print("Food Menu:", self.items)
@staticmethod
def run_events():
for e in FoodType.events:
e.run()
def sub_food(ftype):
class_name = ftype.capitalize()
def __init__(self, items):
FoodType.__init__(self, ftype, items)
globals()[class_name] = type(class_name, (FoodType,), {'__init__': __init__})
# Create dynamic classes
[ sub_food(ftype) for ftype in ["Vegetarian", "Nonvegetarian"] ]
Vegetarian(['Spinach', 'Bitter Guard'])
Nonvegetarian(['Meat', 'Fish'])
FoodType.run_events()
Output
Food Type: Vegetarian Food Menu: ['Spinach', 'Bitter Guard'] Food Type: Nonvegetarian Food Menu: ['Meat', 'Fish']
Explanation:
- sub_food dynamically creates subclasses of FoodType.
- Each subclass can be instantiated and used like normal classes.