Какие методы метакласса (metaclass) вызываются при объявление класса?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Методы метакласса (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 и т.п.).