Написание пользовательских метаклассов
Используйте __new__ и __init__ в метаклассах для настройки создания классов.
«Написание пользовательских метаклассов» — бесплатный урок Python Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Python Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Python Academy содержит 4 уроков всего.
Объявление метакласса
Передайте metaclass=MyMeta в определении класса, чтобы использовать специальный метакласс. Метакласс должен наследоваться от type.
class MyMeta(type):
pass
class Foo(metaclass=MyMeta):
pass
print(type(Foo)) # <class '__main__.MyMeta'>Переопределение __new__
__new__(mcs, name, bases, namespace) создаёт объект класса. Используйте его, чтобы изменить пространство имён до построения класса.
class UpperAttrMeta(type):
def __new__(mcs, name, bases, namespace):
upper = {k.upper(): v for k, v in namespace.items()
if not k.startswith("_")}
return super().__new__(mcs, name, bases, {**namespace, **upper})
class Config(metaclass=UpperAttrMeta):
debug = True
print(Config.DEBUG) # TrueПереопределение __init__
__init__(cls, name, bases, namespace) выполняется после создания класса. Используйте его для настройки после создания, например для регистрации.
_registry = {}
class AutoRegisterMeta(type):
def __init__(cls, name, bases, namespace):
super().__init__(name, bases, namespace)
if bases: # skip the base class itself
_registry[name] = cls
class Handler(metaclass=AutoRegisterMeta): pass
class JSONHandler(Handler): pass
class XMLHandler(Handler): pass
print(_registry) # {'JSONHandler': ..., 'XMLHandler': ...}Обеспечение требований интерфейса
Вызывайте TypeError в __new__, если в теле класса отсутствуют обязательные методы.
class InterfaceMeta(type):
REQUIRED = frozenset({"process", "validate"})
def __new__(mcs, name, bases, ns):
if bases and not mcs.REQUIRED.issubset(ns):
missing = mcs.REQUIRED - ns.keys()
raise TypeError(f"{name} must define: {missing}")
return super().__new__(mcs, name, bases, ns)
class Worker(metaclass=InterfaceMeta):
def process(self): ...
def validate(self): ...Одиночка с помощью метакласса
Переопределите __call__ в метаклассе, чтобы перехватить создание экземпляра и каждый раз возвращать один и тот же экземпляр.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *a, **kw):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*a, **kw)
return cls._instances[cls]
class DB(metaclass=SingletonMeta):
pass
print(DB() is DB()) # TrueДинамическое добавление методов
Внедряйте служебные методы во все классы, использующие метакласс, не изменяя каждый класс отдельно.
class MethodInjectorMeta(type):
def __new__(mcs, name, bases, ns):
ns["describe"] = lambda self: f"I am {type(self).__name__}"
return super().__new__(mcs, name, bases, ns)
class Widget(metaclass=MethodInjectorMeta):
pass
print(Widget().describe()) # I am Widget__prepare__ для упорядоченного пространства имён
Переопределите __prepare__, чтобы предоставить специальное отображение в качестве пространства имён класса, например для сохранения порядка определения или обнаружения дубликатов.
class NoDuplicatesMeta(type):
class _NS(dict):
def __setitem__(self, k, v):
if k in self and not k.startswith("_"):
raise ValueError(f"Duplicate attribute: {k}")
super().__setitem__(k, v)
@classmethod
def __prepare__(mcs, name, bases):
return mcs._NS()Разрешение конфликтов метаклассов
Если у нескольких базовых классов разные метаклассы, Python требует явно использовать наиболее производный метакласс, иначе возникает ошибка конфликта.
class MetaA(type): pass
class MetaB(type): pass
# class Bad(A, B): pass # TypeError: metaclass conflict
class MetaAB(MetaA, MetaB): pass # resolve manually
class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass
class Good(A, B, metaclass=MetaAB): passМетакласс и __init_subclass__
Для регистрации подклассов и простых обработчиков __init_subclass__ проще и не требует метакласса.
class Base:
_registry = []
def __init_subclass__(cls, **kw):
super().__init_subclass__(**kw)
Base._registry.append(cls)
class ChildA(Base): pass
class ChildB(Base): pass
print(Base._registry) # [ChildA, ChildB]Отслеживание доступа к атрибутам
Переопределите __getattribute__ в метаклассе, чтобы перехватывать доступ к атрибутам самого класса, а не его экземпляров.
class TraceMeta(type):
def __getattribute__(cls, name):
val = super().__getattribute__(name)
if not name.startswith("_"):
print(f"Accessing class attr: {name}")
return val
class Config(metaclass=TraceMeta):
HOST = "localhost"
_ = Config.HOST # logs: Accessing class attr: HOSTШаблон сбора полей ORM
ORM в стиле Django используют метаклассы для сбора дескрипторов Field из тела класса в реестр _meta.
class Field:
def __init__(self, col): self.col = col
class ModelMeta(type):
def __new__(mcs, name, bases, ns):
fields = {k: v for k, v in ns.items() if isinstance(v, Field)}
ns["_fields"] = fields
return super().__new__(mcs, name, bases, ns)
class User(metaclass=ModelMeta):
name = Field("name")
email = Field("email")
print(User._fields.keys()) # dict_keys(['name', 'email'])Быстрая проверка
Какой метод в специальном метаклассе вызывается для создания самого объекта класса (до вызова __init__)?
Итоги
Специальные метаклассы переопределяют __new__/__init__, чтобы управлять созданием классов: обеспечивать требования, автоматически регистрировать подклассы, внедрять методы или собирать дескрипторы полей. Для простых задач регистрации предпочитайте __init_subclass__.
Часто задаваемые вопросы
Урок «Написание пользовательских метаклассов» бесплатный?
Да — полный текст урока «Написание пользовательских метаклассов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Python Academy, подпишись на CoddyKit PRO. Курс Python Academy содержит 4 уроков всего.
Чему я научусь в уроке «Написание пользовательских метаклассов»?
Используйте __new__ и __init__ в метаклассах для настройки создания классов. Ты практикуешь Python Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Python Academy?
Предыдущий опыт не требуется. Python Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Написание пользовательских метаклассов»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Python Academy?
Да. Каждый урок Python Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Как создаются классы Python
- Написание пользовательских метаклассов
- Дескрипторы: __get__, __set__, __delete__
- __slots__ и оптимизация памяти