Комментарии (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__ или обычных классов. Используй метаклассы только когда нужно контролировать создание класса на низком уровне.