Шаг 170.
Python: сборник рецептов. Метапрограммирование. Определение метакласса, принимающего необязательные аргументы

    На этом шаге мы рассмотрим особенности определения и использования такого метакласса.

Задача

    Вы хотите определить метакласс, который позволяет предоставлять определениям классов необязательные аргументы, - это может пригодиться, например, для управления и конфигурирования обработки при создании типа.

Решение

    При определении классов 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.




Предыдущий шаг Содержание Следующий шаг