На этом шаге мы рассмотрим особенности определения и использования такого метакласса.
Вы хотите определить метакласс, который позволяет предоставлять определениям классов необязательные аргументы, - это может пригодиться, например, для управления и конфигурирования обработки при создании типа.
При определении классов Python позволяет указывать метакласс с помощью именованного аргумента metaclass в инструкции class. Например, для абстрактных базовых классов это делается так:
from abc import ABCMeta, abstractmethod class IStream(metaclass=ABCMeta): @abstractmethod def read(self, maxsize=None): pass @abstractmethod def write(self, data): pass
Однако в ваших собственных метаклассах могут быть предоставлены дополнительные именованные аргументы:
class Spam(metaclass=MyMeta, debug=True, synchronize=True): . . .
Для поддержки таких именованных аргументов в метаклассе вам нужно убедиться, что вы определили их в методах prepare (), __new__() и __init__(), используя обязательные именованные аргументы:
class MyMeta(type): # Необязательно @classmethod def __prepare__(cls, name, bases, *, debug=False, synchronize=False): # Кастомная обработка . . . return super().__prepare__(name, bases) # Требуется def __new__(cls, name, bases, ns, *, debug=False, synchronize=False): # Кастомная обработка . . . return super().__new__(cls, name, bases, ns) # Требуется def __init__(self, name, bases, ns, *, debug=False, synchronize=False): # Кастомная обработка . . . return super().__init__(name, bases, ns)
Добавление необязательных именованных аргументов в метакласс требует, чтобы вы понимали все шаги создания класса, потому что дополнительные аргументы передаются каждому вовлеченному методу. Метод prepare() вызывается первым и используется для создания пространства имен класса, перед тем как тело любого определения в классе будет обработано. В обычном случае этот метод просто возвращает словарь или другой объект отображения. Метод __new__() используется для создания экземпляра получившегося объекта данного типа. Он вызывается после того, как тело класса полностью выполнено. Метод __init__() вызывается последним и используется для выполнения любых дополнительных шагов инициализации.
При написании метаклассов обычно определяют только один из методов __ new__() или __init__(), но не оба. Однако если дополнительный именованный аргумент должен быть принят, оба метода должны быть предоставлены и иметь совместимые сигнатуры. Дефолтный метод prepare() принимает любой набор именованных аргументов, но игнорирует их. Вам только нужно самостоятельно определить, будут ли дополнительные аргументы как-то влиять на управление созданием пространства имен класса.
Использование обязательных именованных аргументов в этом рецепте отражает тот факт, что такие аргументы будут предоставлены по ключевым словам во время создания класса.
Использование именованных аргументов для конфигурации метакласса может быть рассмотрено в качестве альтернативы использованию переменных класса для решения той же задачи. Например:
class Spam(metaclass=MyMeta): debug = True synchronize = True . . .
Преимущество предоставления таких параметров в качестве аргументов в том, что они не загрязняют пространство имен класса дополнительными именами, которые имеют отношение только к созданию класса, а не к последующему выполнению инструкций в классе. Также они доступны методу prepare(), который запускается до начала обработки любой инструкции в теле класса. Переменные класса, с другой стороны, будут доступны лишь в методах метакласса __new__() и __init__().
На следующем шаге мы рассмотрим принудительную установку аргументной сигнатуры при использовании *args и **kwargs.