In Python, metaclasses are classes that create other classes. They act as blueprints for class creation, allowing you to customize how classes are generated and behave. Here’s a structured breakdown:
Key Concepts
- Default Metaclass:
Every class in Python is created by a metaclass. The default metaclass istype
.
class MyClass: # Implicitly uses `type` as metaclass
pass
- Custom Metaclasses:
You can define your own metaclass by subclassingtype
and overriding its methods (e.g.,__new__
,__init__
,__call__
).
class MyMeta(type):
def __new__(cls, name, bases, namespace):
# Custom logic during class creation
return super().__new__(cls, name, bases, namespace)
- Using a Metaclass:
Specify the metaclass when defining a class:
class MyClass(metaclass=MyMeta):
pass
How Metaclasses Work
__new__
Method:
Called to create the class object. It receives:cls
: The metaclass itself.name
: The class name.bases
: Tuple of parent classes.namespace
: Dictionary of class attributes/methods. Example: Automatically add aversion
attribute to all classes using this metaclass:
class VersionMeta(type):
def __new__(cls, name, bases, namespace):
namespace['version'] = 1.0
return super().__new__(cls, name, bases, namespace)
__init__
Method:
Called to initialize the class after it’s created.
class LoggingMeta(type):
def __init__(cls, name, bases, namespace):
print(f"Class {name} created!")
super().__init__(name, bases, namespace)
__call__
Method:
Controls how class instances are created (e.g., enforce Singleton pattern):
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
Use Cases
- Enforce Coding Standards:
Ensure classes have specific attributes or methods.
class EnforceDocMeta(type):
def __new__(cls, name, bases, namespace):
for key, value in namespace.items():
if callable(value) and not value.__doc__:
raise TypeError(f"Method {key} must have a docstring!")
return super().__new__(cls, name, bases, namespace)
- Automatic Registration:
Register classes in a registry upon creation (e.g., plugins).
class PluginRegistryMeta(type):
registry = {}
def __new__(cls, name, bases, namespace):
new_class = super().__new__(cls, name, bases, namespace)
cls.registry[name] = new_class
return new_class
- ORM Frameworks:
Map class attributes to database columns (used in Django, SQLAlchemy).
class ModelMeta(type):
def __new__(cls, name, bases, namespace):
fields = {k: v for k, v in namespace.items() if isinstance(v, Field)}
namespace['_fields'] = fields
return super().__new__(cls, name, bases, namespace)
When to Use Metaclasses
- Advanced Scenarios: When you need deep control over class behavior.
- Framework Development: For APIs, ORMs, or enforcing patterns.
- Avoid Overuse: Prefer simpler solutions like decorators or inheritance for basic tasks.
Example: Singleton Pattern
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
print("Database connection created!")
db1 = Database() # Output: "Database connection created!"
db2 = Database() # No output; returns existing instance
print(db1 is db2) # True
Key Takeaways
- Metaclasses are for class creation, while class decorators modify existing classes.
- They enable powerful patterns but add complexity. Use sparingly!