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

Как устроен метакласс?

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

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

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

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

Как устроен метакласс?

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

Основные концепции

По умолчанию все классы в Python наследуют от type, который и является метаклассом:

class Person:
    pass

# Проверяем метакласс
print(type(Person))  # <class 'type'>
print(type(Person()))  # <class '__main__.Person'>

# Иерархия:
# Person() -> экземпляр класса Person
# Person -> экземпляр класса type (метакласса)
# type -> экземпляр класса type (самого себя!)

print(isinstance(Person, type))  # True
print(isinstance(Person(), Person))  # True

Как работает создание класса

Если не указан метакласс явно, Python использует type:

# Способ 1: Синтаксис (обычный)
class Person:
    name = 'John'
    
    def greet(self):
        return f'Hello, {self.name}'

# Способ 2: Функция type() (эквивалентно способу 1)
Person = type('Person', (), {
    'name': 'John',
    'greet': lambda self: f'Hello, {self.name}'
})

# Оба способа создают идентичный класс

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

Метакласс наследует от type и переопределяет методы __new__ и __init__:

class Meta(type):
    """Пользовательский метакласс"""
    
    def __new__(mcs, name, bases, namespace):
        """Вызывается ДО создания класса"""
        print(f'Creating class {name}')
        print(f'Bases: {bases}')
        print(f'Attributes: {list(namespace.keys())}')
        
        # Добавим новый атрибут ко всем классам
        namespace['created_with_meta'] = True
        
        # Вызываем родительский __new__
        return super().__new__(mcs, name, bases, namespace)
    
    def __init__(cls, name, bases, namespace):
        """Вызывается ПОСЛЕ создания класса"""
        print(f'Initializing class {name}')
        super().__init__(name, bases, namespace)
    
    def __call__(cls, *args, **kwargs):
        """Вызывается при создании экземпляра класса"""
        print(f'Instantiating {cls.__name__}')
        instance = super().__call__(*args, **kwargs)
        return instance

# Использование метакласса
class Person(metaclass=Meta):
    def __init__(self, name):
        self.name = name

print(Person.created_with_meta)  # True
person = Person('Alice')  # Вызовет __call__

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

class ValidateMeta(type):
    """Метакласс для проверки типов атрибутов"""
    
    def __new__(mcs, name, bases, namespace):
        # Проверяем, что все методы имеют docstring
        for key, value in namespace.items():
            if callable(value) and not value.__doc__:
                raise TypeError(f'Method {key} must have a docstring')
        
        return super().__new__(mcs, name, bases, namespace)

class MyClass(metaclass=ValidateMeta):
    def method_with_doc(self):
        """This method has documentation"""
        pass
    
    def method_without_doc(self):  # TypeError!
        pass

Практический пример 2: Singleton паттерн

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

class Database(metaclass=SingletonMeta):
    def __init__(self, host='localhost'):
        self.host = host
        self.connection = None
    
    def connect(self):
        self.connection = f'Connected to {self.host}'
        return self.connection

# Всегда возвращает один и тот же экземпляр
db1 = Database('localhost')
db2 = Database('127.0.0.1')
print(db1 is db2)  # True! (host остаётся 'localhost')
print(db1.connection)  # Connected to localhost

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

class PluginRegistry(type):
    """Метакласс для автоматической регистрации плагинов"""
    plugins = {}
    
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        
        # Регистрируем все подклассы (кроме базового)
        if bases and bases[0] != object:
            plugin_name = namespace.get('plugin_name', name)
            mcs.plugins[plugin_name] = cls
        
        return cls

class Plugin(metaclass=PluginRegistry):
    """Базовый класс для плагинов"""
    pass

class EmailPlugin(Plugin):
    plugin_name = 'email'
    
    def send(self, message):
        return f'Sending email: {message}'

class SMSPlugin(Plugin):
    plugin_name = 'sms'
    
    def send(self, message):
        return f'Sending SMS: {message}'

# Все плагины автоматически зарегистрированы
print(PluginRegistry.plugins)  # {'email': EmailPlugin, 'sms': SMSPlugin}

def get_plugin(name):
    plugin_class = PluginRegistry.plugins.get(name)
    if not plugin_class:
        raise ValueError(f'Unknown plugin: {name}')
    return plugin_class()

plugin = get_plugin('email')
print(plugin.send('Hello'))  # Sending email: Hello

Практический пример 4: ORM модели (как в Django)

class ModelMeta(type):
    """Метакласс для моделирования ORM"""
    
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        
        # Извлекаем поля из класса
        fields = {}
        for key, value in namespace.items():
            if isinstance(value, Field):
                fields[key] = value
        
        cls._fields = fields
        return cls

class Field:
    def __init__(self, field_type, required=False):
        self.field_type = field_type
        self.required = required

class Model(metaclass=ModelMeta):
    pass

class User(Model):
    name = Field(str, required=True)
    email = Field(str, required=True)
    age = Field(int, required=False)

print(User._fields)  # {'name': Field(...), 'email': Field(...), 'age': Field(...)}

# Валидация на основе метакласса
def validate_data(model_class, data):
    for field_name, field in model_class._fields.items():
        if field.required and field_name not in data:
            raise ValueError(f'Field {field_name} is required')
        if field_name in data and not isinstance(data[field_name], field.field_type):
            raise TypeError(f'Field {field_name} must be {field.field_type.__name__}')
    return True

validate_data(User, {'name': 'Alice', 'email': 'alice@example.com'})

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

✅ Создание фреймворков (Django, SQLAlchemy) ✅ ORM системы и моделирование данных ✅ Декораторы на уровне класса ✅ Регистрация плагинов автоматически ✅ Singleton и другие паттерны проектирования ✅ Валидация структуры класса

❌ Простые задачи (используй декораторы или наследование) ❌ Когда можно решить через классические OOP паттерны ❌ Если код становится запутанным

Важное правило

"Метаклассы — это 99% случаев не то, что тебе нужно. Если ты думаешь, что нужны метаклассы, то не нужны. Только исключения (фреймворк разработчики) знают, когда их использовать." — Tim Peters

Метаклассы — это очень мощный механизм, но они усложняют код. В 99% случаев лучше использовать декораторы, наследование или обычные классы. Метаклассы нужны только при разработке фреймворков и библиотек.

Как устроен метакласс? | PrepBro