在 python 的新式类中,元类可能是最难以理解的功能之一了,不仅元类这个概念难以理解,而且很多讲解和阐述也是说得云里雾里,我看过很多很多篇关于元类和 metaclass 的介绍,但是依旧不是很懂,知道我硬着头皮看了两篇官方的解析之后,才有点理解了,本文我将尝试以一个比较简单的角度来介绍元类,希望不会像我看过的文章一样让读者也在云里雾里。
什么是元类
可能我们在学习 Python 的过程中,我们经常会听过一个概念,那就是“在Python 中,一切都是对象”,那么类呢?类也是对象么?是的,类也是对象,那么类既然是对象的话,它是谁的对象?
这个概念可能有点拗口,但是,事实上类是元类的对象,也就是类的类。元类的实例是类,类的实例是对象,就这么简单,我们可以这么验证:
奇怪了对吧,对于 3 中的 a 的类型是 A,我们毫无异议,但是 A 的类型是 type 是什么回事,那我们就反推一下,是不是可以这么理解:
对,就是这么理解,我们称 a 是类 A 的对象,那么,我们也要称 A 是 type 的对象,那么我们就可以将这里的 type 理解为类的类,也就是大家所说的 元类 了。根据 Python 的官方文档[3],完整的构造形式是这样的:
A = type('A', (object, ), dict({}))
class type(name, bases, dict)
With one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.class.The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.
With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the name attribute; the bases tuple itemizes the base classes and becomes the bases attribute; and the dict dictionary is the namespace containing definitions for class body and is copied to a standard dictionary to become the dict attribute. For example, the following two statements create identical type objects:
元类何时用
虽然,刚才演示的是用 type 来构造类,但是,这并不是 Python 的常用用法,因为这种用法比较麻烦,而且局限性比较大。既然是类的类,我们从上面看到,他可以创建类,那么,能不能不创建类,而只修改类的属性呢?事实上,Python 是可以的,根据 Python 官方文档[4] 所描述,默认情况下,类似通过 type()
来构建的;但是我们也可以通过在定义类的时候指定 metaclass
参数,来修改类的创建过程:
如何确定元类
可能你会奇怪,还需要知道如何确定元类?一个类的元类不是很明确吗?不一定哦,就以我们上面一个例子来说,我们可以很明确知道 MyClass
的元类是 Meta
,那么 MySubclass
的元类呢?好像一下子不能肯定得答上来,下面再看看别人怎么说,还是参考文档[4]:
The appropriate metaclass for a class definition is determined as follows:
- if no bases and no explicit metaclass are given, then type() is used
- if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass
- if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used
根据这些规则,我们就可以清晰得知道,我们的 MySubclass
的元类就是 Meta,基于的是第三条规则!
元类怎么用
和 type 一样,其实我们刚才也看到了,元类定义的时候通常传递三个参数,分别是:
- 类名
- 从基类继承数据的元素
- 属性字典
这里再举个很简单的例子:
都不用创建实例,只需要定义一个类,我们就可以看到一些输出:
这就是元类,可以发现元类是传递了三个参数,分别是:类名/基类列表和属性字典。
元类实战
那么,元类到底有什么用呢?用处其实有很多,我就不自己写实例了,不然可能就会脱离实际,让大家说没有意义,要就来个实际的应用,就以 Flask 为例吧,看下 Flask 里面是如何应用 元类 的,我看的是 Flask 0.12.2 版本,这个版本里面只用了一处的 元类,那就是 View 类里面,代码为:
这里的用法比较奇特,它并没有用到我们上面提到的两种方法中的任何一种,反而是在 Line 9 中使用 type.new,构建了一个实例作为 MethodView
的父类,这种方法在参考资料 [5] 中作了详细介绍。之所以用这种方式,是因为这种方式更加 OOP,因为我们可以在之前的例子里面看到:
class MyClass(metaclass=Meta)
一点也不 OOP,因为无论是在 Java 中还是在 C++ 中,好像都没有说继承列表里面带参数名的吧?