关于python:使用与`type`不同的签名实现元类的正确方法是什么?

What's the correct way to implement a metaclass with a different signature than `type`?

假设我想实现一个应该作为类工厂使用的元类。但是,与使用3个参数的type构造函数不同,我的元类不需要任何参数就可以调用:

1
2
3
Cls1 = MyMeta()
Cls2 = MyMeta()
...

为此,我定义了一个没有参数的自定义__new__方法:

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

但问题是,python自动调用__init__方法,参数与__new__方法相同,因此尝试调用MyMeta()最终引发异常:

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

这是有意义的,因为可以用1或3个参数调用type。但解决这个问题的正确方法是什么?我看见3(4?)选项:

  • 我可以在我的元类中添加一个空的__init__方法,但是由于我不确定type.__init__是否做了重要的事情,这可能不是一个好主意。
  • 我可以实现一个调用super().__init__(cls.__name__, cls.__bases__, vars(cls))__init__方法。
  • 我可以使用一个元类并重写它的__call__方法,而不是与__new____init__混淆。
  • 奖金选项:也许我不应该尝试更改签名?

所以我的问题是:我列出的3个解决方案是否正确,或者其中是否隐藏了一些细微的缺陷?哪种解决方案是最好的(即最正确的)?


在常规类中,偏离父签名的接口也是一种有问题的设计。你不需要超复杂的元类就可以进入这种混乱状态——你可以通过子类化一个datetime或其他东西来产生相同的new/init混乱。

I want to have a metaclass and an easy way to create instances of that metaclass.

在python中,通常的模式是使用from_something类方法编写工厂。以从不同的init签名创建datetime实例为例,有例如datetime.fromtimestamp,但您也有许多其他示例(dict.fromkeysint.from_bytesbytes.fromhex…)

这里没有特定于元类的内容,因此请使用相同的模式:

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

用途:

1
2
3
4
5
6
7
8
9
10
11
>>> 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'