Python 高手编程系列三千四百三十一:元类

Python 高手编程系列三千四百三十一:元类 元类metaclass是一个 Python 特性许多人认为它是这门语言最难的内容之一因此许多程序员都避免使用它。事实上一旦你理解了几个基本概念它并不像听起来那么复杂。作为回报了解这一特性之后你能够完成一些其他方法无法完成的事情。元类是定义其他类型的一种类型。为了理解其工作方式最重要的是要知道定义了对象实例的类也是对象。因此如果它也是对象的话那么一定有与其相关联的类。所有类定义的基类都是内置的 type 类。在 Python 中可以将某个类的元类替换为我们自定义的类型。通常来说新的元类仍然是 type 类的子类参见图 3-4因为如果不是的话这个类将在继承方面与其他的类非常不兼容。一般语法调用内置的 type()类可作为 class 语句的动态等效。给定名称、基类和包含属性的映射它会创建一个新的类对象def method(self):return 1klass 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(metaclasstype):passmetaclass 参数的值通常是另一个类对象但它可以是任意可调用对象只要接受与 type 类相同的参数并返回另一个类对象即可。调用签名为 type(name, bases,namespace)其解释如下。• name这是将保存在__name__属性中的类名称。• bases这是父类的列表将成为__bases__属性并用于构造新创建的类的 MRO。• namespace这是包含类主体定义的命名空间映射将成为__dict__属性。思考元类的一种方式是__new__()方法但是在更高一级的类定义中思考。虽然可以用显式调用 type()的函数来替代元类但通常的做法是使用继承自 type的另一个类。元类的常用模板如下class Metaclass(type):defnew(mcs, name, bases, namespace):return super().new(mcs, name, bases, namespace)classmethoddefprepare(mcs, name, bases, **kwargs):return super().prepare(name, bases, **kwargs)definit(cls, name, bases, namespace, **kwargs):super().init(name, bases, namespace)defcall(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(metaclassMetaclass, extra“value”):pass在一开始如果没有适当的例子这么大的信息量是很难消化的。所以我们将利用一些 print()命令对元类、类和实例的创建过程进行追踪class RevealingMeta(type):defnew(mcs, name, bases, namespace, **kwargs):print(mcs, “newcalled”)return super().new(mcs, name, bases, namespace)classmethoddefprepare(mcs, name, bases, **kwargs):print(mcs, “preparecalled”)return super().prepare(name, bases, **kwargs)definit(cls, name, bases, namespace, **kwargs):print(cls, “initcalled”)super().init(name, bases, namespace)defcall(cls, *args, **kwargs):print(cls, “callcalled”)return super().call(*args, **kwargs)利用 RevealingMeta 作为元类来创建新的类定义在 Python 交互式会话中会给出下列输出class RevealingClass(metaclassRevealingMeta):… def __new __(cls):… print(cls, __new __ called)… return super(). __new __(cls)… def __init __(self):… print(self, __init __ called)… super(). __init __()…class ‘RevealingMeta’ __prepare __ calledclass ‘RevealingMeta’ __new __ calledclass ‘RevealingClass’ __init __ calledinstance RevealingClass()class ‘RevealingClass’ __call __ calledclass ‘RevealingClass’ __new __ calledRevealingClass object at 0x1032b9fd0 __init __ called