Как правильно реализовать метакласс с сигнатурой, отличной от `type`? - PullRequest
0 голосов
/ 05 сентября 2018

Скажем, я хочу реализовать метакласс, который должен служить фабрикой классов. Но в отличие от конструктора type, который принимает 3 аргумента, мой метакласс должен вызываться без каких-либо аргументов:

Cls1 = MyMeta()
Cls2 = MyMeta()
...

Для этой цели я определил пользовательский метод __new__ без параметров:

class MyMeta(type):
    def __new__(cls):
        return super().__new__(cls, 'MyCls', (), {})

Но проблема в том, что python автоматически вызывает метод __init__ с теми же аргументами, что и метод __new__, поэтому попытка вызова MyMeta() приводит к возникновению исключения:

TypeError: type.__init__() takes 1 or 3 arguments

Что имеет смысл, поскольку type можно вызывать с 1 или 3 аргументами. Но как правильно это исправить? Я вижу 3 (4?) Варианта:

  • Я мог бы добавить пустой метод __init__ к своему метаклассу, но так как я не уверен, что type.__init__ делает что-то важное, это может быть не очень хорошая идея.
  • Я мог бы реализовать __init__ метод, который вызывает super().__init__(cls.__name__, cls.__bases__, vars(cls)).
  • Я мог бы использовать мета-метакласс и переопределить его метод __call__, вместо того, чтобы связываться с __new__ и __init__.
  • Бонусный вариант: Может, мне не стоит пытаться изменить подпись?

Итак, мой вопрос: верны ли 3 решения, которые я перечислил, или в них скрыты какие-то тонкие ошибки? Какое решение является лучшим (то есть наиболее правильным)?

1 Ответ

0 голосов
/ 05 сентября 2018

Интерфейс, отличающийся от родительской подписи, - сомнительный дизайн и в обычных классах. Вам не нужна дополнительная сложность метаклассов, чтобы попасть в этот беспорядок - вы можете вызвать тот же новый беспорядок / init, создав подкласс datetime или что-то еще.

Я хочу иметь метакласс и простой способ создания экземпляров этого метакласса.

Обычный шаблон в Python - написать фабрику с использованием from_something метода класса. Чтобы взять пример создания экземпляров datetime из другой сигнатуры инициализации, есть, например, datetime.fromtimestamp, но у вас есть и много других примеров (dict.fromkeys, int.from_bytes, bytes.fromhex ... )

Здесь нет ничего специфичного для метаклассов, поэтому используйте тот же шаблон:

class MyMeta(type):
    @classmethod
    def from_no_args(cls, name=None):
        if name is None:
            name = cls.__name__ + 'Instance'
        return cls(name, (), {})

Использование:

>>> class A(metaclass=MyMeta):
...     pass
... 
>>> B = MyMeta.from_no_args()
>>> C = MyMeta.from_no_args(name='C')
>>> A.__name__
'A'
>>> B.__name__
'MyMetaInstance'
>>> C.__name__
'C'
...