What are metaclasses in Python

онлайн тренажер по питону
Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Metaclasses in Python: The Ultimate Guide for SEO and Developers

Metaclasses in Python are special classes that create and manage other classes. Since everything in Python is an object, including classes, metaclasses can be thought of as "classes for classes". They provide a mechanism for controlling the class creation process and modifying their behavior during the definition stage.

How Metaclasses Work

When Python encounters a class definition, it automatically uses the type metaclass to create that class. The process looks like this:

class MyClass:
    pass

Internally, Python executes:

MyClass = type('MyClass', (), {})

Where:

  • 'MyClass' — the name of the class
  • () — a tuple of parent classes
  • {} — a dictionary of attributes and methods of the class

The built-in type metaclass is responsible for creating all classes in Python by default.

Creating Custom Metaclasses

Custom metaclasses are created by inheriting from type and overriding its methods:

class MyMeta(type):
    def __new__(cls, name, bases, attrs):
        print(f"Creating class {name}")
        attrs['created_by_metaclass'] = True
        return super().__new__(cls, name, bases, attrs)

class MyClass(metaclass=MyMeta):
    pass

print(MyClass.created_by_metaclass)  # True

Key Methods of Metaclasses

__new__

Manages the creation of a class. Called before __init__ and returns the new class:

class CreationMeta(type):
    def __new__(cls, name, bases, attrs):
        # Modifying attributes before class creation
        attrs['class_id'] = id(cls)
        return super().__new__(cls, name, bases, attrs)

__init__

Initializes an already created class:

class InitMeta(type):
    def __init__(cls, name, bases, attrs):
        print(f"Initializing class {name}")
        super().__init__(name, bases, attrs)

__call__

Defines the behavior when creating instances of a class:

class CallMeta(type):
    def __call__(cls, *args, **kwargs):
        print(f"Creating instance {cls.__name__}")
        return super().__call__(*args, **kwargs)

Practical Examples of Use

Automatic Attribute Conversion

class UpperAttrMeta(type):
    def __new__(cls, name, bases, attrs):
        uppercase_attrs = {}
        for attr_name, attr_value in attrs.items():
            if not attr_name.startswith('__'):
                uppercase_attrs[attr_name.upper()] = attr_value
            else:
                uppercase_attrs[attr_name] = attr_value
        return super().__new__(cls, name, bases, uppercase_attrs)

class MyClass(metaclass=UpperAttrMeta):
    attr1 = 'value1'
    attr2 = 'value2'

print(hasattr(MyClass, 'ATTR1'))  # True
print(hasattr(MyClass, 'attr1'))  # False

Implementing the Singleton Pattern

class SingletonMeta(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 DatabaseConnection(metaclass=SingletonMeta):
    def __init__(self):
        print("Creating a database connection")

db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True

Validating Class Structure

class ValidatedMeta(type):
    def __new__(cls, name, bases, attrs):
        # Checking for required methods
        required_methods = ['process', 'validate']
        for method in required_methods:
            if method not in attrs:
                raise TypeError(f"Class {name} must contain method {method}")
        
        return super().__new__(cls, name, bases, attrs)

class DataProcessor(metaclass=ValidatedMeta):
    def process(self):
        pass
    
    def validate(self):
        pass

Automatic Addition of Methods

class AutoMethodMeta(type):
    def __new__(cls, name, bases, attrs):
        # Automatically adding getter and setter methods
        for attr_name, attr_value in list(attrs.items()):
            if not attr_name.startswith('_') and not callable(attr_value):
                # Creating getter
                def make_getter(attr):
                    def getter(self):
                        return getattr(self, f'_{attr}')
                    return getter
                
                # Creating setter
                def make_setter(attr):
                    def setter(self, value):
                        setattr(self, f'_{attr}', value)
                    return setter
                
                attrs[f'get_{attr_name}'] = make_getter(attr_name)
                attrs[f'set_{attr_name}'] = make_setter(attr_name)
        
        return super().__new__(cls, name, bases, attrs)

class Person(metaclass=AutoMethodMeta):
    name = None
    age = None

p = Person()
p.set_name("Иван")
print(p.get_name())

Alternatives to Metaclasses

Class Decorators

For many tasks, class decorators can be a simpler solution:

def add_methods(cls):
    cls.new_method = lambda self: "New method"
    return cls

@add_methods
class MyClass:
    pass

obj = MyClass()
print(obj.new_method())

__init_subclass__

Starting with Python 3.6, you can use __init_subclass__:

class BaseClass:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.registered = True

class ChildClass(BaseClass):
    pass

print(ChildClass.registered)

When to Use Metaclasses

Metaclasses should be used in the following cases:

  • Frameworks and libraries — to create APIs with automatic configuration
  • ORM systems — for automatically creating fields and methods
  • Design patterns — when standard approaches are insufficient
  • Architecture validation — to ensure compliance with design rules
  • Metaprogramming — when you need to create code programmatically

Usage Recommendations

  • Use with caution — metaclasses complicate the code and can make debugging difficult
  • Consider alternatives — often decorators or __init_subclass__ are more appropriate
  • Document behavior — be sure to describe what your metaclass does
  • Test thoroughly — metaclasses can lead to unexpected behavior
  • Follow the KISS principle — if you can solve the problem more simply, it is better to do so

Impact on Performance

Metaclasses can slightly slow down class creation because they add an extra layer of processing. However, this effect is usually imperceptible, as classes are mainly created when the application starts.

Debugging Metaclasses

For debugging metaclasses, it is helpful to add logging:

import logging

class DebugMeta(type):
    def __new__(cls, name, bases, attrs):
        logging.info(f"Creating class {name}")
        logging.info(f"Base classes: {bases}")
        logging.info(f"Attributes: {list(attrs.keys())}")
        return super().__new__(cls, name, bases, attrs)

Metaclasses are a powerful tool for managing the class creation process in Python. They allow you to implement complex design patterns and provide automatic configuration of classes. However, they should be used consciously when simple solutions are not suitable, as they can significantly complicate the project architecture.

News