Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

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.

Levels: Advanced ~28 min read

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.

python
1
2
3
4
5
6
class Plain:
    pass

item = Plain()
print(type(item).__name__)
print(type(Plain).__name__)

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.

python
1
2
3
4
5
6
attributes = {"category": "report"}
Report = type("Report", (), attributes)

report = Report()
print(report.category)
print(type(Report).__name__)

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.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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)

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.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class PluginMeta(type):
    def __new__(mcls, name, bases, namespace):
        if name != "Plugin" and "plugin_name" not in namespace:
            raise TypeError("Plugins must define plugin_name")
        return super().__new__(mcls, name, bases, namespace)

class Plugin(metaclass=PluginMeta):
    pass

class CsvPlugin(Plugin):
    plugin_name = "csv"

print(CsvPlugin.plugin_name)
print(type(CsvPlugin).__name__)

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.

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class LeftMeta(type):
    pass

class RightMeta(type):
    pass

class Left(metaclass=LeftMeta):
    pass

class Right(metaclass=RightMeta):
    pass

try:
    class Combined(Left, Right):
        pass
except TypeError as error:
    print(type(error).__name__)

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; type is 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 type and delegates through super().__new__() after applying a class-wide rule.
  • Use __init_subclass__(), class decorators, or descriptors instead when they provide a simpler solution.
Was this page helpful?

Mark this learning resource complete to track your learning progress.