在 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 一样,其实我们刚才也看到了,元类定义的时候通常传递三个参数,分别是:

这里再举个很简单的例子:

15039763574338

都不用创建实例,只需要定义一个类,我们就可以看到一些输出:

这就是元类,可以发现元类是传递了三个参数,分别是:类名/基类列表和属性字典。

元类实战

那么,元类到底有什么用呢?用处其实有很多,我就不自己写实例了,不然可能就会脱离实际,让大家说没有意义,要就来个实际的应用,就以 Flask 为例吧,看下 Flask 里面是如何应用 元类 的,我看的是 Flask 0.12.2 版本,这个版本里面只用了一处的 元类,那就是 View 类里面,代码为:

这里的用法比较奇特,它并没有用到我们上面提到的两种方法中的任何一种,反而是在 Line 9 中使用 type.new,构建了一个实例作为 MethodView 的父类,这种方法在参考资料 [5] 中作了详细介绍。之所以用这种方式,是因为这种方式更加 OOP,因为我们可以在之前的例子里面看到:

class MyClass(metaclass=Meta)

一点也不 OOP,因为无论是在 Java 中还是在 C++ 中,好像都没有说继承列表里面带参数名的吧?

Reference

  1. Core Python
  2. Understanding Python metaclasses
  3. Python Docs
  4. Customizing Class Creation
  5. What is a metaclass in Python?
  6. What is a metaclass in Python? 中文