元类(metaclass)是一个 Python 特性,许多人认为它是这门语言最难的内容之一,因 此许多程序员都避免使用它。事实上,一旦你理解了几个基本概念,它并不像听起来那么 复杂。作为回报,了解这一特性之后,你能够完成一些其他方法无法完成的事情。
元类是定义其他类(型)的一种类(型)。为了理解其工作方式,最重要的是要知道, 定义了对象实例的类也是对象。因此,如果它也是对象的话,那么一定有与其相关联的类。 所有类定义的基类都是内置的 type 类。图 3-3 这张简单的图应该可以说清楚。
在 Python 中,可以将某个类的元类替换为我们自定义的类型。通常来说,新的元类仍 然是 type 类的子类(参见图 3-4),因为如果不是的话,这个类将在继承方面与其他的类 非常不兼容。
1.一般语法
调用内置的 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,说明它已经
90 第3章 语法最佳实践—类级别以上
是一个创建好的类对象(元类的实例),而不是一个元类对象。__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