Understand metaclasses in Python
Learn how metaclasses create classes, how metaclass __call__ affects instance construction, and when simpler subclass hooks are sufficient.
A metaclass is the type of a class object. Ordinary classes usually have type as their metaclass; a class with a custom metaclass has that custom type instead. Metaclasses can customize class creation and what happens when the resulting class is called.
Distinguish class creation from instance construction
class Example:
pass
print(type(Example) is type) # True
print(type(Example()) is Example) # True
The class statement creates Example. Calling Example() creates and initializes an instance through the metaclass's call machinery. These are separate operations.
Cache instances in a metaclass
class Singleton(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 Logger(metaclass=Singleton):
pass
first = Logger()
second = Logger()
print(first is second) # True in this single-threaded example
This illustrates overriding metaclass __call__, not a universal singleton guarantee. Concurrent first calls can race, separate processes have separate caches, and later constructor arguments are ignored after the first instance is cached. The cache also retains instances. Use it only when those semantics fit the design.
Customize class creation
class Versioned(type):
def __new__(mcls, name, bases, namespace, **kwargs):
namespace["version"] = "1.0"
return super().__new__(mcls, name, bases, namespace, **kwargs)
class Component(metaclass=Versioned):
pass
print(Component.version) # 1.0
The metaclass's __new__ creates the class from its name, bases, and namespace. Its __init__ can initialize the resulting class object; neither is the ordinary instance's initializer. Preserve framework-provided namespace entries when customizing creation, including class-cell information needed by zero-argument super().
Prefer a smaller hook when it is enough
Metaclasses appear in declarative frameworks, registration systems, and abstract-class machinery. For many subclass registries, __init_subclass__ is sufficient:
class Plugin:
registry = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.registry.append(cls)
class FilePlugin(Plugin):
pass
This records subclasses when they are created. A class decorator is another option when an explicit transformation is clearer. These approaches can avoid metaclass conflicts when combining frameworks.
FAQ
How does this differ from inheritance? Inheritance determines how a class obtains behavior from its bases. A metaclass determines how the class object is constructed and behaves as an object itself.
Is type the metaclass of every class? No. type(type) is type, but type(Logger) is Singleton in the example. The one-argument type form returns an object's type; its three-argument form can create a class dynamically.
Do I need a custom metaclass for application code? Usually an ordinary class, decorator, or subclass hook is enough. Understanding metaclasses remains useful when reading a framework that controls class creation.
References
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan