在深入研究 Python 的官方文档后,我发现 Python 3.x 提供了一种将参数传递给元类的本机方法,尽管它并非没有缺陷。
只需将其他关键字参数添加到类声明中即可:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
...它们会像这样传递到您的元类中:
class MyMetaClass(type):
@classmethod
def __prepare__(metacls, name, bases, **kargs):
#kargs = {"myArg1": 1, "myArg2": 2}
return super().__prepare__(name, bases, **kargs)
def __new__(metacls, name, bases, namespace, **kargs):
#kargs = {"myArg1": 1, "myArg2": 2}
return super().__new__(metacls, name, bases, namespace)
#DO NOT send "**kargs" to "type.__new__". It won't catch them and
#you'll get a "TypeError: type() takes 1 or 3 arguments" exception.
def __init__(cls, name, bases, namespace, myArg1=7, **kargs):
#myArg1 = 1 #Included as an example of capturing metaclass args as positional args.
#kargs = {"myArg2": 2}
super().__init__(name, bases, namespace)
#DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older. You'll get a
#"TypeError: type.__init__() takes no keyword arguments" exception.
你必须离开kargs
通话中type.__new__
and type.__init__
(Python 3.5 及更早版本;请参阅下面的“更新”)或者会给你一个TypeError
由于传递太多参数而引发异常。这意味着,当以这种方式传递元类参数时,我们总是必须实现MyMetaClass.__new__
and MyMetaClass.__init__
防止我们的自定义关键字参数到达基类type.__new__
and type.__init__
方法。type.__prepare__
似乎可以优雅地处理额外的关键字参数(这就是为什么我在示例中传递它们,以防万一有一些我不知道的功能依赖于**kargs
),所以定义type.__prepare__
是可选的。
UPDATE
在Python 3.6中,出现了type
被调整并且type.__init__
现在可以优雅地处理额外的关键字参数。您仍然需要定义type.__new__
(抛出TypeError: __init_subclass__() takes no keyword arguments
例外)。
分解
在 Python 3 中,您可以通过关键字参数而不是类属性指定元类:
class MyClass(metaclass=MyMetaClass):
pass
这句话大致翻译为:
MyClass = metaclass(name, bases, **kargs)
...在哪里metaclass
是您传入的“元类”参数的值,name
是你的班级的字符串名称('MyClass'
), bases
是您传入的任何基类(零长度元组()
在这种情况下),并且kargs
是任何未捕获的关键字参数(空的dict
{}
在这种情况下)。
进一步细分,该声明大致可翻译为:
namespace = metaclass.__prepare__(name, bases, **kargs) #`metaclass` passed implicitly since it's a class method.
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kargs)
metaclass.__init__(MyClass, name, bases, namespace, **kargs)
...在哪里kargs
始终是dict
我们传递给类定义的未捕获的关键字参数。
分解我上面给出的例子:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
...大致翻译为:
namespace = MyMetaClass.__prepare__('C', (), myArg1=1, myArg2=2)
#namespace={'__module__': '__main__', '__qualname__': 'C'}
C = MyMetaClass.__new__(MyMetaClass, 'C', (), namespace, myArg1=1, myArg2=2)
MyMetaClass.__init__(C, 'C', (), namespace, myArg1=1, myArg2=2)
这些信息大部分来自Python 关于“自定义类创建”的文档 https://docs.python.org/3.3/reference/datamodel.html#customizing-class-creation.