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

Какие методы метакласса (metaclass) вызываются при объявление класса?

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

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

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

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

Методы метакласса (metaclass), вызываемые при объявлении класса

Метакласс — это класс класса. Он контролирует создание и поведение класса точно так же, как класс контролирует экземпляр. Разберу порядок вызова методов.

1. __prepare__

Вызывается первым перед созданием тела класса. Возвращает namespace (словарь), в который попадают все атрибуты класса.

class TracingMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        print(f"__prepare__ вызван для класса {name}")
        namespace = {}
        namespace["_trace"] = []
        return namespace

class MyClass(metaclass=TracingMeta):
    x = 10  # Попадёт в namespace
    def method(self):
        pass

print(MyClass._trace)  # Можно использовать переменную из __prepare__

Это полезно для отслеживания порядка определения атрибутов.

2. __new__

Вызывается для создания объекта класса (самого класса, а не его экземпляра). Возвращает новый класс.

class ValidationMeta(type):
    def __new__(mcs, name, bases, dct, **kwargs):
        print(f"__new__ вызван для класса {name}")
        
        # Проверка: все методы должны быть с docstring
        for attr_name, attr_value in dct.items():
            if callable(attr_value) and not attr_value.__doc__:
                raise TypeError(f"Метод {attr_name} не имеет docstring")
        
        # Создаём сам класс
        cls = super().__new__(mcs, name, bases, dct)
        cls._created_at = "2024-01-20"
        return cls

class GoodClass(metaclass=ValidationMeta):
    def method(self):
        """Это необходимый docstring"""
        pass

print(GoodClass._created_at)  # 2024-01-20

3. __init__

Вызывается после __new__ для инициализации класса. Получает тот же класс, который вернул __new__.

class LoggingMeta(type):
    def __init__(cls, name, bases, dct, **kwargs):
        print(f"__init__ вызван для класса {name}")
        super().__init__(name, bases, dct)
        
        # Оборачиваем все методы в логирование
        for attr_name in dir(cls):
            attr = getattr(cls, attr_name)
            if callable(attr) and not attr_name.startswith("_"):
                def make_logged(original):
                    def logged_version(*args, **kwargs):
                        print(f"Вызван {original.__name__}")
                        return original(*args, **kwargs)
                    return logged_version
                setattr(cls, attr_name, make_logged(attr))

class MyClass(metaclass=LoggingMeta):
    def do_something(self):
        print("Делаю что-то")

obj = MyClass()
obj.do_something()  # Выведет логи

Порядок вызова при объявлении класса

class MyMeta(type):
    @classmethod
    def __prepare__(mcs, name, bases, **kwargs):
        print(f"1. __prepare__ для {name}")
        return {}
    
    def __new__(mcs, name, bases, dct, **kwargs):
        print(f"2. __new__ для {name}")
        return super().__new__(mcs, name, bases, dct)
    
    def __init__(cls, name, bases, dct, **kwargs):
        print(f"3. __init__ для {name}")
        super().__init__(name, bases, dct)

print("Объявляю класс:")
class MyClass(metaclass=MyMeta):
    x = 10

# Вывод:
# Объявляю класс:
# 1. __prepare__ для MyClass
# 2. __new__ для MyClass
# 3. __init__ для MyClass

4. __call__ (бонус)

Вызывается при создании экземпляра класса (это уже на этапе экземпляра, но хорошо знать).

class SingletonMeta(type):
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        print(f"__call__ для создания экземпляра {cls.__name__}")
        if cls not in cls._instances:
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]

class Singleton(metaclass=SingletonMeta):
    def __init__(self):
        print("__init__ экземпляра")
        self.value = 42

obj1 = Singleton()  # Создаёт экземпляр
obj2 = Singleton()  # Возвращает тот же экземпляр
print(obj1 is obj2)  # True

Практический пример: автоматическая регистрация плагинов

class PluginMeta(type):
    plugins = {}
    
    def __new__(mcs, name, bases, dct):
        cls = super().__new__(mcs, name, bases, dct)
        
        # Регистрируем класс при создании (если не базовый класс)
        if bases:  # Пропускаем сам Plugin класс
            plugin_name = dct.get("name", name)
            mcs.plugins[plugin_name] = cls
            print(f"Зарегистрирован плагин: {plugin_name}")
        
        return cls

class Plugin(metaclass=PluginMeta):
    def execute(self):
        raise NotImplementedError

class PDFPlugin(Plugin):
    name = "pdf"
    def execute(self):
        return "Обработка PDF"

class ImagePlugin(Plugin):
    name = "image"
    def execute(self):
        return "Обработка изображения"

print(PluginMeta.plugins)
# {"pdf": <class PDFPlugin>, "image": <class ImagePlugin>}

Важные моменты

  • __prepare__ — контролирует namespace перед обработкой тела класса
  • __new__ — создаёт сам класс, может изменить структуру
  • __init__ — инициализирует класс после его создания
  • Порядок: __prepare__ → вычисляется тело класса → __new____init__

Метаклассы используются редко в обычном коде, но критичны для фреймворков (Django ORM, SQLAlchemy и т.п.).