虽然这个问题是关于Python 2.7的,已经有了一个很好的答案,但我对Python 3.3也有同样的问题,而且在谷歌上找到的最接近答案的线程就是这个。通过查阅Python官方文档,我发现Python 3.x提供了一种本地方法来传递参数给元类,尽管它并不完美。只需在类声明中添加其他关键字参数即可。
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
他们会被传递到你的元类中,如下所示:
class MyMetaClass(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
return super().__prepare__(name, bases, **kwargs)
def __new__(metacls, name, bases, namespace, **kwargs):
return super().__new__(metacls, name, bases, namespace)
def __init__(cls, name, bases, namespace, myArg1=7, **kwargs):
super().__init__(name, bases, namespace)
您需要在调用
type.__new__
和
type.__init__
时将
kwargs
排除在外(适用于Python 3.5及更早版本;请参见“更新”),否则会因传递过多参数而导致
TypeError
异常。这意味着,当以这种方式传递元类参数时,我们总是必须实现
MyMetaClass.__new__
和
MyMetaClass.__init__
,以防止我们的自定义关键字参数到达基类
type.__new__
和
type.__init__
方法。
type.__prepare__
似乎能够优雅地处理额外的关键字参数(因此在示例中通过它传递它们,以防有一些我不知道但依赖于
**kwargs
的功能),因此定义
type.__prepare__
是可选的。
更新
在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, **kwargs)
...其中 metaclass
是你传入的 "metaclass" 参数的值,name
是你的类的字符串名称(例如 'MyClass'
),bases
是任何你传入的基类(在本例中是一个长度为零的元组 ()
),kwargs
是任何未捕获的关键字参数(在本例中是一个空的 dict
{}
)。
进一步分解这个语句,大致翻译为:
namespace = metaclass.__prepare__(name, bases, **kwargs)
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kwargs)
metaclass.__init__(MyClass, name, bases, namespace, **kwargs)
...其中kwargs
总是我们传递给类定义的未捕获关键字参数的dict
。
分解我上面给出的例子:
class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
pass
大致翻译为:
namespace = MyMetaClass.__prepare__('C', (), myArg1=1, myArg2=2)
C = MyMetaClass.__new__(MyMetaClass, 'C', (), namespace, myArg1=1, myArg2=2)
MyMetaClass.__init__(C, 'C', (), namespace, myArg1=1, myArg2=2)
大部分信息来自
Python文档中的“自定义类创建”。
__metaclass__
。 - Thomas Orozco