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

Как создать простой метакласс?

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

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

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

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

Как создать простой метакласс

Метакласс — это класс, который создаёт классы. В то время как обычный класс создаёт объекты, метакласс создаёт классы. Это мощный инструмент для метапрограммирования, но требует осторожности.

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

# Обычная иерархия
obъект = Класс()      # Класс создаёт объект

# С метаклассами
Класс = Метакласс()  # Метакласс создаёт класс

# Все классы в Python — экземпляры метакласса type
class MyClass:
    pass

print(type(MyClass))  # <class 'type'>
print(type(MyClass())) # <class '__main__.MyClass'>

2. Самый простой метакласс

class SimpleMeta(type):
    """Простейший метакласс — просто логирует"""
    def __new__(mcs, name, bases, namespace):
        print(f"Creating class {name}")
        return super().__new__(mcs, name, bases, namespace)

# Использование
class MyClass(metaclass=SimpleMeta):
    pass

# Output:
# Creating class MyClass

obj = MyClass()
print(isinstance(obj, MyClass))  # True
print(type(MyClass))  # <class '__main__.SimpleMeta'>

3. Параметры метакласса: new

class InspectMeta(type):
    """Метакласс, инспектирующий класс"""
    def __new__(mcs, name, bases, namespace):
        # name: имя класса ('MyClass')
        # bases: кортеж базовых классов ((object,))
        # namespace: словарь с атрибутами и методами
        
        print(f"Class name: {name}")
        print(f"Base classes: {bases}")
        print(f"Attributes: {list(namespace.keys())}")
        
        return super().__new__(mcs, name, bases, namespace)

class MyClass(InspectMeta):
    x = 10
    
    def method(self):
        pass

# Output:
# Class name: MyClass
# Base classes: (<class 'object'>,)
# Attributes: ['__module__', '__qualname__', 'x', 'method']

4. Метакласс init

class LoggingMeta(type):
    """Логирует вызовы методов класса"""
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        return cls
    
    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        print(f"Initialized class {name}")
    
    def __call__(cls, *args, **kwargs):
        """Вызывается при создании экземпляра"""
        print(f"Creating instance of {cls.__name__}")
        instance = super().__call__(*args, **kwargs)
        print(f"Instance created successfully")
        return instance

class MyClass(metaclass=LoggingMeta):
    def __init__(self):
        print("MyClass.__init__ called")

obj = MyClass()

# Output:
# Initialized class MyClass
# Creating instance of MyClass
# MyClass.__init__ called
# Instance created successfully

5. Валидация атрибутов

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

# Используем
class GoodClass(metaclass=ValidatingMeta):
    def good_method(self):
        """This method has a docstring"""
        pass

try:
    class BadClass(metaclass=ValidatingMeta):
        def bad_method(self):
            pass  # No docstring!
except ValueError as e:
    print(e)  # Method BadClass.bad_method must have a docstring

6. Автоматическое добавление методов

class AutoReprMeta(type):
    """Автоматически добавляет __repr__"""
    def __new__(mcs, name, bases, namespace):
        def auto_repr(self):
            attrs = ', '.join(
                f"{k}={getattr(self, k)!r}"
                for k in sorted(vars(self).keys())
            )
            return f"{name}({attrs})"
        
        # Добавляем __repr__ если его нет
        if '__repr__' not in namespace:
            namespace['__repr__'] = auto_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(age=30, name='Alice')

7. Метакласс для 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):
        self.connection = "connected"

db1 = Database()
db2 = Database()

print(db1 is db2)  # True — один и тот же экземпляр
print(db1.connection)  # connected

8. Метакласс с регистрацией подклассов

class PluginMeta(type):
    """Метакласс для автоматической регистрации плагинов"""
    plugins = {}
    
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        
        # Регистрируем класс
        if 'plugin_name' in namespace:
            mcs.plugins[namespace['plugin_name']] = cls
        
        return cls
    
    @classmethod
    def get_plugin(mcs, name):
        return mcs.plugins.get(name)
    
    @classmethod
    def list_plugins(mcs):
        return list(mcs.plugins.keys())

class BasePlugin(metaclass=PluginMeta):
    plugin_name = None

class PDFPlugin(BasePlugin):
    plugin_name = 'pdf'
    
    def process(self):
        return "Processing PDF"

class ImagePlugin(BasePlugin):
    plugin_name = 'image'
    
    def process(self):
        return "Processing image"

print(PluginMeta.list_plugins())  # ['pdf', 'image']
pdf = PluginMeta.get_plugin('pdf')
print(pdf().process())  # Processing PDF

9. Метакласс с кешированием

class CachingMeta(type):
    """Кеширует результаты методов класса"""
    def __new__(mcs, name, bases, namespace):
        # Оборачиваем все методы в кэширование
        for attr_name, attr_value in namespace.items():
            if callable(attr_value) and not attr_name.startswith('_'):
                namespace[attr_name] = mcs.cache_method(attr_value)
        
        return super().__new__(mcs, name, bases, namespace)
    
    @staticmethod
    def cache_method(func):
        cache = {}
        
        def wrapper(*args, **kwargs):
            key = (args, tuple(sorted(kwargs.items())))
            if key not in cache:
                cache[key] = func(*args, **kwargs)
            return cache[key]
        
        return wrapper

class Calculator(metaclass=CachingMeta):
    def fibonacci(self, n):
        print(f"Computing fibonacci({n})")
        if n <= 1:
            return n
        return self.fibonacci(n - 1) + self.fibonacci(n - 2)

calc = Calculator()
print(calc.fibonacci(5))  # Вычисляет
print(calc.fibonacci(5))  # Берёт из кеша (без "Computing fibonacci")

10. Метакласс с наследованием

class VerboseMeta(type):
    def __new__(mcs, name, bases, namespace):
        print(f"Creating {name}")
        return super().__new__(mcs, name, bases, namespace)

class VerboseObject(metaclass=VerboseMeta):
    pass

class MyClass(VerboseObject):
    pass

class MySubClass(MyClass):
    pass

# Output:
# Creating VerboseObject
# Creating MyClass
# Creating MySubClass

Best Practices

# ✓ Хорошо: Используй для специфичных задач
class ORMMeta(type):
    """Метакласс для ORM"""
    def __new__(mcs, name, bases, namespace):
        # Обрабатываем декораторы @field
        return super().__new__(mcs, name, bases, namespace)

# ✗ Плохо: Используй когда есть проще решение
class PointlessMeta(type):
    def __new__(mcs, name, bases, namespace):
        print(f"Class {name} created")
        return super().__new__(mcs, name, bases, namespace)
# Просто используй __init_subclass__ вместо этого!

# ✓ Хорошо: Документируй
class DocumentedMeta(type):
    """Метакласс, который регистрирует классы.
    
    Используется для автоматической регистрации view функций.
    """
    pass

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

class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        print(f"Subclass {cls.__name__} created")

class Child(Base):
    pass

# Output:
# Subclass Child created

# Это проще чем метакласс для большинства случаев

Заключение

Метаклассы — это продвинутый инструмент для создания фреймворков и DSL. Для большинства задач достаточно __init_subclass__ или обычных классов. Используй метаклассы только когда нужно контролировать создание класса на низком уровне.

Как создать простой метакласс? | PrepBro