元类(metaclass)是一个 Python 特性,许多人认为它是这门语言最难的内容之一,因
此许多程序员都避免使用它。事实上,一旦你理解了几个基本概念,它并不像听起来那么
复杂。作为回报,了解这一特性之后,你能够完成一些其他方法无法完成的事情。
元类是定义其他类(型)的一种类(型)。为了理解其工作方式,最重要的是要知道,
定义了对象实例的类也是对象。因此,如果它也是对象的话,那么一定有与其相关联的类。
所有类定义的基类都是内置的 type 类。
在 Python 中,可以将某个类的元类替换为我们自定义的类型。通常来说,新的元类仍
然是 type 类的子类(参见图 3-4),因为如果不是的话,这个类将在继承方面与其他的类
非常不兼容。
一般语法
调用内置的 type()类可作为 class 语句的动态等效。给定名称、基类和包含属性的映射,它会创建一个新的类对象:
def method(self):
return 1
klass = type(‘MyClass’, (object,), {‘method’: method})
其输出如下:
instance = klass()
instance.method()
1
这种写法等价于类的显式定义:
class MyClass:
def method(self):
return 1
你会得到如下结果:
instance = MyClass()
instance.method()
1
用 class 语句创建的每个类都隐式地使用 type 作为其元类。可以通过向 class 语
句提供 metaclass 关键字参数来改变这一默认行为:
class ClassWithAMetaclass(metaclass=type):
pass
metaclass 参数的值通常是另一个类对象,但它可以是任意可调用对象,只要接受
与 type 类相同的参数并返回另一个类对象即可。调用签名为 type(name, bases,
namespace),其解释如下。
• name:这是将保存在__name__属性中的类名称。
• bases:这是父类的列表,将成为__bases__属性,并用于构造新创建的类的 MRO。
• namespace:这是包含类主体定义的命名空间(映射),将成为__dict__属性。
思考元类的一种方式是__new__()方法,但是在更高一级的类定义中思考。
虽然可以用显式调用 type()的函数来替代元类,但通常的做法是使用继承自 type
的另一个类。元类的常用模板如下:
class Metaclass(type):
def new(mcs, name, bases, namespace):
return super().new(mcs, name, bases, namespace)
@classmethod
def prepare(mcs, name, bases, **kwargs):
return super().prepare(name, bases, **kwargs)
def init(cls, name, bases, namespace, **kwargs):
super().init(name, bases, namespace)
def call(cls, *args, **kwargs):
return super().call(*args, **kwargs)
name、bases 和 namespace 参数的含义与前面介绍的 type()调用中的参数相同,
但以下 4 个方法的作用却各不相同。
• new(mcs, name, bases, namespace):复杂类对象的实际创建,其创
建方式与普通类相同。第一个位置参数是一个元类对象。在上面的例子中,它就是
Metaclass。注意,mcs 是这一参数常用的命名约定。
• prepare(mcs, name, bases, **kwargs):这会创建一个空的命名空间
对象。默认返回一个空的 dict,但可以覆写并使其返回其他任何映射类型。注意,
它不接受 namespace 作为参数,因为在调用它之前命名空间并不存在。
• init(cls, name, bases, namespace, **kwargs):这在元类实现中
并不常见,但其含义与普通类中的含义相同。一旦__new__()创建完成,它可以执
行其他类对象初始化过程。现在第一个位置参数的命名约定为 cls,说明它已经是一个创建好的类对象(元类的实例),而不是一个元类对象。init()被调
用时,类已经构建完成,所以这一方法可以做的事情比__new__()要少。实现这样
的方法非常类似于使用类装饰器,但主要的区别在于,每个子类都会调用
init(),而类装饰器则不会被子类调用。
• call(cls, *args, kwargs):当调用元类实例时会调用这一方法。元
类实例是一个类对象(参见图 3-3),在创建类的新实例时会调用它。这一方法可
用于覆写类实例创建和初始化的默认方式。
上述所有方法都可以接受额外的关键字参数(这里用kwargs 表示)。这些参数可以
在类定义中通过额外的关键字参数传入到元类对象中,其代码如下:
class Klass(metaclass=Metaclass, extra=“value”):
pass
在一开始,如果没有适当的例子,这么大的信息量是很难消化的。所以我们将利用一
些 print()命令对元类、类和实例的创建过程进行追踪:
class RevealingMeta(type):
def new(mcs, name, bases, namespace, **kwargs):
print(mcs, “new called”)
return super().new(mcs, name, bases, namespace)
@classmethod
def prepare(mcs, name, bases, **kwargs):
print(mcs, “prepare called”)
return super().prepare(name, bases, **kwargs)
def init(cls, name, bases, namespace, **kwargs):
print(cls, “init called”)
super().init(name, bases, namespace)
def call(cls, *args, **kwargs):
print(cls, “call called”)
return super().call(*args, **kwargs)
利用 RevealingMeta 作为元类来创建新的类定义,在 Python 交互式会话中会给出
下列输出:
class RevealingClass(metaclass=RevealingMeta):
… def __new __(cls):
… print(cls, " __new __ called")
… return super(). __new __(cls)
… def __init __(self):
… print(self, " __init __ called")
… super(). __init __()
…
<class ‘RevealingMeta’> __prepare __ called
<class ‘RevealingMeta’> __new __ called
<class ‘RevealingClass’> __init __ called
instance = RevealingClass()
<class ‘RevealingClass’> __call __ called
<class ‘RevealingClass’> __new __ called
<RevealingClass object at 0x1032b9fd0> __init __ called