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

Зачем нужен метакласс (metaclass)?

2.8 Senior🔥 101 комментариев
#Python Core#Архитектура и паттерны

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

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

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

Зачем нужен метакласс (metaclass)

Метакласс - это класс для классов. Если объекты - это экземпляры класса, то классы - это экземпляры метакласса. Метаклассы используются редко, но для некоторых задач они необходимы.

Основная идея

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

dog = Dog()  # dog - экземпляр класса Dog

# Метакласс - класс для класса
class Meta(type):
    pass

class Cat(metaclass=Meta):
    pass

cat = Cat()  # cat - экземпляр класса Cat
# Cat - экземпляр метакласса Meta

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

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

class MyClass(metaclass=Meta):
    pass

obj = 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 = "MySQL Connection"

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

Практический пример 2: ORM - автоматическая регистрация

class ModelMeta(type):
    registry = {}
    
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if name != 'Model':  # Не регистрируем базовый класс
            mcs.registry[name] = cls
        return cls

class Model(metaclass=ModelMeta):
    pass

class User(Model):
    table = "users"
    fields = ["id", "name"]

class Post(Model):
    table = "posts"
    fields = ["id", "title"]

print(ModelMeta.registry)  # {'User': <class User>, 'Post': <class Post>}

Практический пример 3: Валидация методов

class ValidatorMeta(type):
    def __new__(mcs, name, bases, namespace):
        for key, value in namespace.items():
            if callable(value) and not key.startswith('_'):
                if not hasattr(value, '__doc__') or not value.__doc__:
                    raise ValueError(f"Метод {key} должен иметь docstring")
        return super().__new__(mcs, name, bases, namespace)

class MyAPI(metaclass=ValidatorMeta):
    def get_user(self):
        """Получить пользователя"""  # OK
        pass
    
    def delete_user(self):  # Ошибка!
        pass

Практический пример 4: Автоматический repr

class AutoReprMeta(type):
    def __new__(mcs, name, bases, namespace):
        if '__repr__' not in namespace:
            def __repr__(self):
                attrs = ', '.join(f"{k}={getattr(self, k)}" 
                                 for k in self.__dict__)
                return f"{name}({attrs})"
            namespace['__repr__'] = __repr__
        return super().__new__(mcs, name, bases, namespace)

class Person(metaclass=AutoReprMeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
print(p)  # Person(name=Alice, age=30)

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

  1. Singleton - одиночка паттерн
  2. ORM - SQLAlchemy, Django ORM используют метаклассы
  3. Фреймворки - автоматическая регистрация, валидация
  4. Декораторы на уровне класса - масштабируемо
  5. Проверка контрактов - валидация интерфейсов

Когда НЕ использовать

  • Если можно решить через обычное наследование
  • Если можно использовать декоратор функции
  • Если код станет сложнее для понимания

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

# Вместо метакласса - можно использовать __init_subclass__
class Model:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        print(f"Регистрирую класс {cls.__name__}")

class User(Model):  # Сработает __init_subclass__
    pass

Правило

"Метаклассы - это огонь. Если вы думаете, что нужен метакласс, то на 99% вам он не нужен." (Тим Петерс, создатель Python)

Используй метаклассы только если ты точно знаешь, что делаешь, и более простые решения не подходят.