Writing Custom Metaclasses
Use __new__ and __init__ in metaclasses to customize class creation.
Writing Custom Metaclasses is a free Python Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Python Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Declaring a Metaclass
Pass metaclass=MyMeta in the class definition to use a custom metaclass. The metaclass must inherit from type.
class MyMeta(type):
pass
class Foo(metaclass=MyMeta):
pass
print(type(Foo)) # <class '__main__.MyMeta'>Overriding __new__
__new__(mcs, name, bases, namespace) creates the class object. Use it to modify the namespace before the class is built.
class UpperAttrMeta(type):
def __new__(mcs, name, bases, namespace):
upper = {k.upper(): v for k, v in namespace.items()
if not k.startswith("_")}
return super().__new__(mcs, name, bases, {**namespace, **upper})
class Config(metaclass=UpperAttrMeta):
debug = True
print(Config.DEBUG) # TrueOverriding __init__
__init__(cls, name, bases, namespace) runs after the class is created. Use it for post-creation setup like registration.
_registry = {}
class AutoRegisterMeta(type):
def __init__(cls, name, bases, namespace):
super().__init__(name, bases, namespace)
if bases: # skip the base class itself
_registry[name] = cls
class Handler(metaclass=AutoRegisterMeta): pass
class JSONHandler(Handler): pass
class XMLHandler(Handler): pass
print(_registry) # {'JSONHandler': ..., 'XMLHandler': ...}Enforcing Interface Contracts
Raise TypeError in __new__ if required methods are missing from the class body.
class InterfaceMeta(type):
REQUIRED = frozenset({"process", "validate"})
def __new__(mcs, name, bases, ns):
if bases and not mcs.REQUIRED.issubset(ns):
missing = mcs.REQUIRED - ns.keys()
raise TypeError(f"{name} must define: {missing}")
return super().__new__(mcs, name, bases, ns)
class Worker(metaclass=InterfaceMeta):
def process(self): ...
def validate(self): ...Singleton via Metaclass
Override __call__ on the metaclass to intercept instance creation and return the same instance every time.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *a, **kw):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*a, **kw)
return cls._instances[cls]
class DB(metaclass=SingletonMeta):
pass
print(DB() is DB()) # TrueAdding Methods Dynamically
Inject utility methods into every class that uses the metaclass without modifying each class individually.
class MethodInjectorMeta(type):
def __new__(mcs, name, bases, ns):
ns["describe"] = lambda self: f"I am {type(self).__name__}"
return super().__new__(mcs, name, bases, ns)
class Widget(metaclass=MethodInjectorMeta):
pass
print(Widget().describe()) # I am Widget__prepare__ for Ordered Namespace
Override __prepare__ to supply a custom mapping as the class namespace, e.g., to preserve definition order or detect duplicates.
class NoDuplicatesMeta(type):
class _NS(dict):
def __setitem__(self, k, v):
if k in self and not k.startswith("_"):
raise ValueError(f"Duplicate attribute: {k}")
super().__setitem__(k, v)
@classmethod
def __prepare__(mcs, name, bases):
return mcs._NS()Metaclass Conflict Resolution
When multiple bases have different metaclasses, Python requires the most-derived metaclass to be used explicitly, or a conflict error is raised.
class MetaA(type): pass
class MetaB(type): pass
# class Bad(A, B): pass # TypeError: metaclass conflict
class MetaAB(MetaA, MetaB): pass # resolve manually
class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass
class Good(A, B, metaclass=MetaAB): passMetaclass vs __init_subclass__
For subclass registration and simple hooks, __init_subclass__ is simpler and requires no metaclass.
class Base:
_registry = []
def __init_subclass__(cls, **kw):
super().__init_subclass__(**kw)
Base._registry.append(cls)
class ChildA(Base): pass
class ChildB(Base): pass
print(Base._registry) # [ChildA, ChildB]Tracing Attribute Access
Override __getattribute__ on the metaclass to intercept attribute access on the class (not on instances).
class TraceMeta(type):
def __getattribute__(cls, name):
val = super().__getattribute__(name)
if not name.startswith("_"):
print(f"Accessing class attr: {name}")
return val
class Config(metaclass=TraceMeta):
HOST = "localhost"
_ = Config.HOST # logs: Accessing class attr: HOSTORM Field Collection Pattern
Django-style ORMs use metaclasses to collect Field descriptors from the class body into a _meta registry.
class Field:
def __init__(self, col): self.col = col
class ModelMeta(type):
def __new__(mcs, name, bases, ns):
fields = {k: v for k, v in ns.items() if isinstance(v, Field)}
ns["_fields"] = fields
return super().__new__(mcs, name, bases, ns)
class User(metaclass=ModelMeta):
name = Field("name")
email = Field("email")
print(User._fields.keys()) # dict_keys(['name', 'email'])Quick Check
In a custom metaclass, which method is called to create the class object itself (before __init__)?
Recap
Custom metaclasses override __new__/__init__ to control class creation: enforce contracts, auto-register subclasses, inject methods, or collect field descriptors. Prefer __init_subclass__ for simple registration needs.
Frequently asked questions
Is the “Writing Custom Metaclasses” lesson free?
Yes — the full text of “Writing Custom Metaclasses” is free to read here on the web, and the Python Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Python Academy course, upgrade to CoddyKit PRO.
What will I learn in “Writing Custom Metaclasses”?
Use __new__ and __init__ in metaclasses to customize class creation. You practise Python Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Python Academy?
No prior experience is required. Python Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Writing Custom Metaclasses” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Python Academy lesson?
Yes. Every Python Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- How Python Classes Are Created
- Writing Custom Metaclasses
- Descriptors: __get__, __set__, __delete__
- __slots__ and Memory Optimization