← Назад к вопросам

Как работает метакласс (metaclass)?

2.7 Senior🔥 121 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает метакласс (metaclass)

Метакласс — это класс, который определяет поведение другого класса. Если класс — это шаблон для создания объектов, то метакласс — это шаблон для создания классов. В Python всё является объектом, включая классы, и метаклассы управляют их созданием и поведением.

Основная концепция

# Обычный класс
class MyClass:
    pass

# MyClass — это объект, тип которого — type
print(type(MyClass))  # <class 'type'>
print(isinstance(MyClass, type))  # True

# Стандартный метакласс для всех классов в Python — type
print(type(int))  # <class 'type'>
print(type(str))  # <class 'type'>

Все классы в Python (кроме старых классов Python 2) наследуются от type. Когда вы определяете класс, Python вызывает метакласс для его создания.

Создание собственного метакласса

# Метакласс — это класс, который наследуется от type
class MetaClass(type):
    def __new__(mcs, name, bases, namespace):
        # mcs — сам метакласс
        # name — имя создаваемого класса
        # bases — базовые классы
        # namespace — словарь атрибутов класса
        
        print(f"Создание класса: {name}")
        print(f"Методы: {[key for key in namespace if not key.startswith('_')]}")
        
        return super().__new__(mcs, name, bases, namespace)

# Использование метакласса
class MyClass(metaclass=MetaClass):
    def method(self):
        pass

# Вывод:
# Создание класса: MyClass
# Методы: ['method']

Метод init метакласса

class DebugMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Вызывается при создании класса
        print(f"__new__: Создание {name}")
        return super().__new__(mcs, name, bases, namespace)
    
    def __init__(cls, name, bases, namespace):
        # Инициализация после создания класса
        print(f"__init__: Инициализация {name}")
        super().__init__(name, bases, namespace)
    
    def __call__(cls, *args, **kwargs):
        # Вызывается при создании экземпляра класса
        print(f"__call__: Создание экземпляра {cls.__name__}")
        return super().__call__(*args, **kwargs)

class MyClass(metaclass=DebugMeta):
    pass

# Создание экземпляра
obj = MyClass()

# Вывод:
# __new__: Создание MyClass
# __init__: Инициализация MyClass
# __call__: Создание экземпляра MyClass

Практические примеры метаклассов

1. Singleton через метакласс

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 Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = None

db1 = Database()
db2 = Database()
print(db1 is db2)  # True — один и тот же объект

2. Автоматическая регистрация подклассов

class Registry(type):
    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        
        if not hasattr(cls, '_registry'):
            cls._registry = {}
        else:
            # Регистрируем все подклассы
            cls._registry[name] = cls
    
    @classmethod
    def get_class(mcs, name):
        return mcs._registry.get(name)

class Plugin(metaclass=Registry):
    pass

class TextPlugin(Plugin):
    pass

class ImagePlugin(Plugin):
    pass

print(Plugin._registry)  # {'TextPlugin': <class '__main__.TextPlugin'>, 'ImagePlugin': <class '__main__.ImagePlugin'>}

3. Валидация методов при создании класса

class ValidatedMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Проверяем, что все методы начинаются с do_
        for key, value in namespace.items():
            if callable(value) and not key.startswith('_'):
                if not key.startswith('do_'):
                    raise TypeError(f"Метод {key} должен начинаться с 'do_'")
        
        return super().__new__(mcs, name, bases, namespace)

try:
    class BadClass(metaclass=ValidatedMeta):
        def invalid_method(self):
            pass
except TypeError as e:
    print(e)  # Метод invalid_method должен начинаться с 'do_'

class GoodClass(metaclass=ValidatedMeta):
    def do_something(self):
        pass

4. Автоматическое добавление логирования

class LoggingMeta(type):
    def __new__(mcs, name, bases, namespace):
        for key, value in namespace.items():
            if callable(value) and not key.startswith('_'):
                namespace[key] = mcs._add_logging(value)
        
        return super().__new__(mcs, name, bases, namespace)
    
    @staticmethod
    def _add_logging(func):
        def wrapper(self, *args, **kwargs):
            print(f"Вызов: {func.__name__}({args}, {kwargs})")
            result = func(self, *args, **kwargs)
            print(f"Результат: {result}")
            return result
        return wrapper

class MyService(metaclass=LoggingMeta):
    def process(self, data):
        return data * 2

service = MyService()
service.process(5)

# Вывод:
# Вызов: process((5,), {})
# Результат: 10

Когда использовать метаклассы

Используй метаклассы когда:

  • Нужно контролировать создание класса, а не объекта
  • Необходимо модифицировать определение класса при его создании
  • Нужна глобальная регистрация классов
  • Требуется валидация структуры класса

Альтернативы:

  • Декораторы классов (проще и понятнее)
  • Миксины (для добавления поведения)
  • Дескрипторы (для контроля доступа к атрибутам)
# Вместо метакласса часто используют декоратор класса
def add_logging(cls):
    for key, value in cls.__dict__.items():
        if callable(value) and not key.startswith('_'):
            setattr(cls, key, lambda self, *a, v=value: (print(f"Вызов {v.__name__}"), v(self, *a))[1])
    return cls

@add_logging
class Service:
    def process(self):
        return "OK"

Заключение

Метаклассы — мощный, но сложный инструмент. Они позволяют контролировать создание и поведение классов, но в большинстве случаев проблема может быть решена проще: декораторами, миксинами или дескрипторами. Помните: "Metaclasses are deeper magic than 99% of users should ever worry about" (Tim Peters).