Skip to content

How to Add Attributes in Python Metaclasses

Learn how to automatically inject attributes into Python classes using metaclasses, including dynamic attribute generation, class validation, and registry patterns.

Metaclasses are one of Python's most powerful and advanced features. They let you customize how classes are created, enabling you to automatically add attributes, validate class definitions, register classes, or enforce coding standards at the moment a class comes into existence.

This guide explains what metaclasses are, how they work, and demonstrates several ways to add attributes to classes through metaclasses.

Prerequisites

Before diving into metaclasses, you should be comfortable with:

  • Python classes and objects (OOP fundamentals)
  • The type() function
  • Class inheritance
  • Magic methods like __init__ and __new__

Understanding Metaclasses

In Python, everything is an object including classes themselves. When you define a class, Python creates a class object using a metaclass. By default, that metaclass is type.

The relationship is:

type (metaclass) → creates → Class (object) → creates → Instance (object)
class Dog:
    pass

d = Dog()

print(type(d))      # <class '__main__.Dog'> where d is an instance of Dog
print(type(Dog))    # <class 'type'> where Dog is an instance of type
print(type(type))   # <class 'type'> where type is an instance of itself

Output:

<class '__main__.Dog'>
<class 'type'>
<class 'type'>

A metaclass is simply a class whose instances are classes. By creating a custom metaclass, you can control how classes are constructed.

Creating a Basic Metaclass

A metaclass is defined by inheriting from type. Override the __new__ method to customize class creation:

class MyMeta(type):
    def __new__(cls, name, bases, namespace):
        print(f"Creating class: {name}")
        return super().__new__(cls, name, bases, namespace)

class MyClass(metaclass=MyMeta):
    pass

Output:

Creating class: MyClass

The __new__ Parameters

Parameter Description
cls The metaclass itself (MyMeta)
name The name of the class being created ('MyClass')
bases Tuple of base classes (parent classes)
namespace Dictionary of the class body (attributes and methods)

Adding Attributes via __new__

The most common way to add attributes through a metaclass is inside the __new__ method. Attributes added here appear on every class that uses the metaclass:

class AutoAttributeMeta(type):
    def __new__(cls, name, bases, namespace):
        obj = super().__new__(cls, name, bases, namespace)

        obj.created_by = "AutoAttributeMeta"
        obj.version = "1.0"

        return obj

class Product(metaclass=AutoAttributeMeta):
    pass

class Service(metaclass=AutoAttributeMeta):
    pass

print(f"Product.created_by: {Product.created_by}")
print(f"Product.version: {Product.version}")
print(f"Service.created_by: {Service.created_by}")

Output:

Product.created_by: AutoAttributeMeta
Product.version: 1.0
Service.created_by: AutoAttributeMeta

Both Product and Service automatically have created_by and version attributes, even though they weren't defined in their class bodies.

Adding Attributes as Metaclass Class Variables

You can also define attributes directly on the metaclass itself. Classes using that metaclass can access them through the standard attribute lookup chain:

class ConfigMeta(type):
    debug_mode = False
    log_level = "INFO"

    def __new__(cls, name, bases, namespace):
        obj = super().__new__(cls, name, bases, namespace)
        obj.framework = "MyFramework"
        return obj

class AppConfig(metaclass=ConfigMeta):
    pass

print("Framework:", AppConfig.framework)

print("Debug mode:", ConfigMeta.debug_mode)
print("Log level:", ConfigMeta.log_level)

Output:

Framework: MyFramework
Debug mode: False
Log level: INFO

Adding Dynamic Attributes Based on Class Content

One of the most powerful use cases is inspecting the class body and adding attributes dynamically based on what the class defines:

class AutoCountMeta(type):
    def __new__(cls, name, bases, namespace):
        obj = super().__new__(cls, name, bases, namespace)

        methods = [key for key, val in namespace.items()
                   if callable(val) and not key.startswith('_')]
        obj._method_count = len(methods)
        obj._method_names = methods

        return obj

class Calculator(metaclass=AutoCountMeta):
    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b

    def multiply(self, a, b):
        return a * b

print(f"Calculator has {Calculator._method_count} methods: {Calculator._method_names}")

Output:

Calculator has 3 methods: ['add', 'subtract', 'multiply']

The metaclass automatically counts and records the public methods defined in the class.

Adding Attributes After Class Creation

You can add attributes to a metaclass (or its classes) at any time, just like with regular classes:

class DemoMeta(type):
    attr1 = 1

    def __new__(cls, name, bases, namespace):
        obj = super().__new__(cls, name, bases, namespace)
        obj.attr2 = 2
        return obj

class Demo(metaclass=DemoMeta):
    pass

print(f"attr1: {Demo.attr1}, attr2: {Demo.attr2}")

DemoMeta.attr3 = 3

print(f"attr3: {Demo.attr3}")

Output:

attr1: 1, attr2: 2
attr3: 3

Practical Example: Auto-Registration Pattern

A common real-world use of metaclasses is automatically registering classes in a registry when they're created:

class PluginMeta(type):
    registry = {}

    def __new__(cls, name, bases, namespace):
        obj = super().__new__(cls, name, bases, namespace)

        if bases:
            cls.registry[name] = obj
            obj.registered = True
            obj.plugin_name = name.lower()

        return obj

class Plugin(metaclass=PluginMeta):
    """Base class for all plugins."""
    pass

class AudioPlugin(Plugin):
    def process(self):
        return "Processing audio"

class VideoPlugin(Plugin):
    def process(self):
        return "Processing video"

print("Registered plugins:", list(PluginMeta.registry.keys()))
print(f"AudioPlugin.plugin_name: {AudioPlugin.plugin_name}")
print(f"VideoPlugin.registered: {VideoPlugin.registered}")

plugin = PluginMeta.registry['AudioPlugin']()
print(plugin.process())

Output:

Registered plugins: ['AudioPlugin', 'VideoPlugin']
AudioPlugin.plugin_name: audioplugin
VideoPlugin.registered: True
Processing audio

Practical Example: Enforcing Required Attributes

Metaclasses can validate that classes define certain attributes:

class ValidatedMeta(type):
    required_attrs = []

    def __new__(cls, name, bases, namespace):
        if bases:
            for attr in cls.required_attrs:
                if attr not in namespace:
                    raise TypeError(
                        f"Class '{name}' must define '{attr}' attribute"
                    )

        return super().__new__(cls, name, bases, namespace)

class ModelMeta(ValidatedMeta):
    required_attrs = ['table_name', 'fields']

class BaseModel(metaclass=ModelMeta):
    pass

class User(BaseModel):
    table_name = "users"
    fields = ["id", "name", "email"]

print(f"User table: {User.table_name}")
print(f"User fields: {User.fields}")

Output:

User table: users
User fields: ['id', 'name', 'email']
class InvalidModel(BaseModel):
    table_name = "products"

Output:

TypeError: Class 'InvalidModel' must define 'fields' attribute

Conclusion

Metaclasses give you complete control over class creation in Python, allowing you to automatically add attributes, validate class definitions, register classes, and enforce patterns across your codebase.

Add attributes by setting them in __new__ for per-class attributes, or on the metaclass itself for shared configuration.

For real-world use cases like plugin registration, ORM models, and framework design, metaclasses are a powerful tool, but always consider simpler alternatives like __init_subclass__ or class decorators first, and reach for metaclasses only when you truly need to customize the class creation process.

Navigation