Metaclasses are one of Python's most powerful and advanced features. They let you customize how classes are created, enabling you to automatically add attributes, validate class definitions, register classes, or enforce coding standards at the moment a class comes into existence.
This guide explains what metaclasses are, how they work, and demonstrates several ways to add attributes to classes through metaclasses.
Prerequisites
Before diving into metaclasses, you should be comfortable with:
- Python classes and objects (OOP fundamentals)
- The
type()function - Class inheritance
- Magic methods like
__init__and__new__
Understanding Metaclasses
In Python, everything is an object including classes themselves. When you define a class, Python creates a class object using a metaclass. By default, that metaclass is type.
The relationship is:
type (metaclass) → creates → Class (object) → creates → Instance (object)class Dog:
pass
d = Dog()
print(type(d)) # <class '__main__.Dog'> where d is an instance of Dog
print(type(Dog)) # <class 'type'> where Dog is an instance of type
print(type(type)) # <class 'type'> where type is an instance of itselfOutput:
<class '__main__.Dog'>
<class 'type'>
<class 'type'>A metaclass is simply a class whose instances are classes. By creating a custom metaclass, you can control how classes are constructed.
Creating a Basic Metaclass
A metaclass is defined by inheriting from type. Override the __new__ method to customize class creation:
class MyMeta(type):
def __new__(cls, name, bases, namespace):
print(f"Creating class: {name}")
return super().__new__(cls, name, bases, namespace)
class MyClass(metaclass=MyMeta):
passOutput:
Creating class: MyClassThe __new__ Parameters
| Parameter | Description |
|---|---|
cls |
The metaclass itself (MyMeta) |
name |
The name of the class being created ('MyClass') |
bases |
Tuple of base classes (parent classes) |
namespace |
Dictionary of the class body (attributes and methods) |
Adding Attributes via __new__
The most common way to add attributes through a metaclass is inside the __new__ method. Attributes added here appear on every class that uses the metaclass:
class AutoAttributeMeta(type):
def __new__(cls, name, bases, namespace):
obj = super().__new__(cls, name, bases, namespace)
obj.created_by = "AutoAttributeMeta"
obj.version = "1.0"
return obj
class Product(metaclass=AutoAttributeMeta):
pass
class Service(metaclass=AutoAttributeMeta):
pass
print(f"Product.created_by: {Product.created_by}")
print(f"Product.version: {Product.version}")
print(f"Service.created_by: {Service.created_by}")Output:
Product.created_by: AutoAttributeMeta
Product.version: 1.0
Service.created_by: AutoAttributeMetaBoth Product and Service automatically have created_by and version attributes, even though they weren't defined in their class bodies.
Adding Attributes as Metaclass Class Variables
You can also define attributes directly on the metaclass itself. Classes using that metaclass can access them through the standard attribute lookup chain:
class ConfigMeta(type):
debug_mode = False
log_level = "INFO"
def __new__(cls, name, bases, namespace):
obj = super().__new__(cls, name, bases, namespace)
obj.framework = "MyFramework"
return obj
class AppConfig(metaclass=ConfigMeta):
pass
print("Framework:", AppConfig.framework)
print("Debug mode:", ConfigMeta.debug_mode)
print("Log level:", ConfigMeta.log_level)Output:
Framework: MyFramework
Debug mode: False
Log level: INFOAdding Dynamic Attributes Based on Class Content
One of the most powerful use cases is inspecting the class body and adding attributes dynamically based on what the class defines:
class AutoCountMeta(type):
def __new__(cls, name, bases, namespace):
obj = super().__new__(cls, name, bases, namespace)
methods = [key for key, val in namespace.items()
if callable(val) and not key.startswith('_')]
obj._method_count = len(methods)
obj._method_names = methods
return obj
class Calculator(metaclass=AutoCountMeta):
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
print(f"Calculator has {Calculator._method_count} methods: {Calculator._method_names}")Output:
Calculator has 3 methods: ['add', 'subtract', 'multiply']The metaclass automatically counts and records the public methods defined in the class.
Adding Attributes After Class Creation
You can add attributes to a metaclass (or its classes) at any time, just like with regular classes:
class DemoMeta(type):
attr1 = 1
def __new__(cls, name, bases, namespace):
obj = super().__new__(cls, name, bases, namespace)
obj.attr2 = 2
return obj
class Demo(metaclass=DemoMeta):
pass
print(f"attr1: {Demo.attr1}, attr2: {Demo.attr2}")
DemoMeta.attr3 = 3
print(f"attr3: {Demo.attr3}")Output:
attr1: 1, attr2: 2
attr3: 3Practical Example: Auto-Registration Pattern
A common real-world use of metaclasses is automatically registering classes in a registry when they're created:
class PluginMeta(type):
registry = {}
def __new__(cls, name, bases, namespace):
obj = super().__new__(cls, name, bases, namespace)
if bases:
cls.registry[name] = obj
obj.registered = True
obj.plugin_name = name.lower()
return obj
class Plugin(metaclass=PluginMeta):
"""Base class for all plugins."""
pass
class AudioPlugin(Plugin):
def process(self):
return "Processing audio"
class VideoPlugin(Plugin):
def process(self):
return "Processing video"
print("Registered plugins:", list(PluginMeta.registry.keys()))
print(f"AudioPlugin.plugin_name: {AudioPlugin.plugin_name}")
print(f"VideoPlugin.registered: {VideoPlugin.registered}")
plugin = PluginMeta.registry['AudioPlugin']()
print(plugin.process())Output:
Registered plugins: ['AudioPlugin', 'VideoPlugin']
AudioPlugin.plugin_name: audioplugin
VideoPlugin.registered: True
Processing audioPractical Example: Enforcing Required Attributes
Metaclasses can validate that classes define certain attributes:
class ValidatedMeta(type):
required_attrs = []
def __new__(cls, name, bases, namespace):
if bases:
for attr in cls.required_attrs:
if attr not in namespace:
raise TypeError(
f"Class '{name}' must define '{attr}' attribute"
)
return super().__new__(cls, name, bases, namespace)
class ModelMeta(ValidatedMeta):
required_attrs = ['table_name', 'fields']
class BaseModel(metaclass=ModelMeta):
pass
class User(BaseModel):
table_name = "users"
fields = ["id", "name", "email"]
print(f"User table: {User.table_name}")
print(f"User fields: {User.fields}")Output:
User table: users
User fields: ['id', 'name', 'email']class InvalidModel(BaseModel):
table_name = "products"Output:
TypeError: Class 'InvalidModel' must define 'fields' attributeConclusion
Metaclasses give you complete control over class creation in Python, allowing you to automatically add attributes, validate class definitions, register classes, and enforce patterns across your codebase.
Add attributes by setting them in __new__ for per-class attributes, or on the metaclass itself for shared configuration.
For real-world use cases like plugin registration, ORM models, and framework design, metaclasses are a powerful tool, but always consider simpler alternatives like __init_subclass__ or class decorators first, and reach for metaclasses only when you truly need to customize the class creation process.