Python Metaclasses and Class Creation
Understand how Python creates class objects, how metaclasses control that process, and when simpler class hooks are a better choice.
Advanced
A class is an object too. The object created by a class statement has a type, just as an instance does. That type is called the class’s
metaclass. For ordinary classes, the metaclass is usually
type. A custom metaclass lets you influence how class objects are built; it is a deeper tool than changing how an instance behaves.
A class has a type of its own
Compare the type of an instance with the type of the class that creates it.
type(value) answers “what type is this object?”, while
type(MyClass) reveals the metaclass used for
MyClass. The class object
Plain is an instance of
type; a normal instance is an instance of
Plain.
|
|
The instance has type Plain, while the class object Plain has metaclass type.
Quick check
For a class defined without a custom metaclass, which object is normally its metaclass?
class Message:
pass
print(type(Message).__name__)
Use type() to build a class object
The three-argument form
type(name, bases, namespace) creates a class object.
name is the class name,
bases is a tuple of base classes, and
namespace supplies its initial attributes. This is different from the one-argument form
type(value), which reports an object’s type. A class statement is clearer for most everyday code; the three-argument form is useful when the class must be assembled dynamically.
|
|
The new class has no base classes, inherits the default metaclass type, and receives category from the supplied namespace.
Quick check
What does the three-argument type(name, bases, namespace) form return?
Record = type("Record", (), {"kind": "event"})
print(Record.kind)
Understand the class-creation sequence
When Python executes a class definition, it resolves the bases and chooses a compatible metaclass. If that metaclass provides
__prepare__(), Python asks it for the namespace in which to execute the class body; otherwise, Python uses the normal ordered namespace. The body then runs, and Python calls the metaclass to create the class object.
On the standard
type.__new__() path, descriptors that define
__set_name__() are notified first, then the parent’s
__init_subclass__() hook runs. Class decorators are applied after the class object has been created; the resulting object is then bound to the class name. The existing descriptor, subclass-hook, and decorator lessons own those features’ details—the point here is how they fit in the sequence.
|
|
The namespace is prepared before the body runs. The metaclass then receives that completed namespace to build the class.
Quick check
In the trace example, which event happens between prepare and create?
events = []
class TraceMeta(type):
@classmethod
def __prepare__(mcls, name, bases, **kwargs):
events.append("prepare")
return {}
def __new__(mcls, name, bases, namespace, **kwargs):
events.append("create")
return super().__new__(mcls, name, bases, namespace)
class Sample(metaclass=TraceMeta):
events.append("body")
print(events)
Create classes with a metaclass
A custom metaclass is normally a subclass of
type. Its
__new__() method receives
mcls (the metaclass), the proposed class
name, its
bases, and its prepared
namespace. This
__new__() creates a **class object**. Do not confuse it with the ordinary
__new__(cls, ...) from the previous lesson, which creates an instance of a class. Delegating to
super().__new__(mcls, name, bases, namespace) preserves normal class construction.
|
|
The inherited metaclass validates subclasses when each class object is created. CsvPlugin is a class object whose metaclass is PluginMeta.
Quick check
In PluginMeta.__new__, what is the purpose of calling super().__new__(mcls, name, bases, namespace)?
class RegistryMeta(type):
def __new__(mcls, name, bases, namespace):
return super().__new__(mcls, name, bases, namespace)
class Entry(metaclass=RegistryMeta):
pass
print(type(Entry).__name__)
Metaclass selection and conflicts
A class inherits its metaclass from its bases unless the definition specifies one. When more than one base contributes a metaclass, Python must find a candidate that is a subtype of all the competing metaclasses. If no such compatible candidate exists, class creation raises
TypeError. This is a deliberate guard against combining incompatible class-construction rules.
|
|
Two unrelated metaclasses cannot automatically govern one class. A compatible metaclass hierarchy is required.
Quick check
What happens if Python cannot select a metaclass compatible with all base classes?
class A(type):
pass
class B(type):
pass
class First(metaclass=A):
pass
class Second(metaclass=B):
pass
try:
class Both(First, Second):
pass
except TypeError:
print("conflict")
Prefer the least powerful mechanism that solves the problem
A metaclass changes the machinery that creates class objects, so it can affect every class that uses it and can make inheritance harder to reason about. Reach for one only when the class-construction process itself must be customized. If the goal is to configure or validate subclasses,
__init_subclass__() is often simpler; to transform one particular class, a class decorator may be enough; for reusable attribute behavior, consider a descriptor.
Keep the scope narrow:
__instancecheck__() and
__subclasscheck__() have their own lesson, as do
__new__() for ordinary instances,
__set_name__(), and
__init_subclass__(). This lesson introduces the class-construction layer that connects those topics without replacing their dedicated explanations.
Key takeaways
-
A metaclass is the type of a class object;
typeis the default metaclass for ordinary classes. -
The three-argument
type(name, bases, namespace)form dynamically creates a class object. - Class construction prepares a namespace, executes the class body, creates the class object, runs creation hooks, applies class decorators, and binds the result.
-
A custom metaclass typically subclasses
typeand delegates throughsuper().__new__()after applying a class-wide rule. -
Use
__init_subclass__(), class decorators, or descriptors instead when they provide a simpler solution.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.