Python元类

有人称元类是Python中最难懂、最高深莫测的概念,也有人说元类是Python中使用率最低的语法之一。
这篇文章中就分享下笔者对元类的理解与应用的一些经验。

怎么理解“元类”

在提到具体的语法之前,先来分析下“元类”这两个字到底意味着什么。

元类的英文是Meta Class

先来看“类(Class)”,一个“类”字就道出了“元类”的本质,它首先是一个“类”,一个class。

再来看“元(Meta)”,从中文上理解或许有些困难,但是看英文Meta就很好理解了.最常见的是Meta Data,表示元数据。学术点说,元数据是用来描述数据属性的数据。

什么意思呢,举个例子说,我有一组数据记录了全班同学的身高、体重以及性别:

姓名 身高 体重
张三 180 50
李四 176 55

看到这张表格的时候,虽然我们很容易就能理解这张表所表达的信息,但是其实这里缺少了关键性的信息,170,180,50,55的单位并没有给出来。我们只是因为很熟悉这样的数据,所以一看这个数值就推测出了他的单位。可如果使用英尺、英镑或者其他的单位来表示的话,那么就会让人困惑了。

所以应该增添下面的数据:

1
2
3
4
{
"身高": "厘米",
"体重": "千克"
}

这就叫做元数据,用来具体描述数据的属性,就叫做元数据。当然元数据不仅仅是用来指示单位这么简单,用来描述数据的各种数据都可以叫做元数据。元数据也不仅仅用来描述数据,还能够描述数据的使用环境、管理、加工、保存和使用等方面的情况

如:全体数据的平均值可以叫做元数据,中位数、最大最小值也是元数据。在数据库如MySQL中存储的索引叫做元数据。

那么到这里,元类的概念也就清晰了,元类是类的元数据。在定义好类之外,我们可以使用元类来更好的增强、扩展类。

Python一切皆对象

在谈元类的使用之前,还需要介绍下Python中“一切皆对象”的特点。

Python的初学者,不论你是否学习过其他的语言,可能刚开始的时候都没有意识到这一特性,而python的强大灵活性也正是来源于此。

我们来看如下代码

1
2
3
4
5
6
7
def add(x, y):
return x + y

class Person:
def __init__(self, name, gender):
self.name = name
self.gender = gender

这里定义了一个函数add,一个类Person。类似的代码转化成C、C++、Java或者其他的什么语言都是很容易的(当然C语言中是没有class的),可以写出完全等价的代码,并且在使用上、概念上也并无不同。

这里最大的区别就在于,在python中,函数与类也是对象

怎么去理解这一点呢?

1
2
3
4
5
6
7
add_1 = add

result = add_1(1, 2)

P = Person

jack = P("jack", "male")

在python中是可以将一个函数、类像一个变量一样进行赋值的。这一特性在其他绝大多数语言中是没有的。因为在python中,函数、类确确实实就是一个对象,一个对象当然可以放在赋值符号的右边。所以当你写下def class之后应该意识到,在这里事实上是创建了一个对象,这个对象在本质上与intfloatstrlist没有任何区别,只是这个对象使用了特殊的语法创建。

元类

明确了“类与函数”都是一个对象这一点之后,再来看元类。

既然也是一个对象,那么下面开始就将称为类对象,虽然有点拗口,但是有助于我们理解这些概念。

当我们在代码中写下class ...时,就是对一个类对象进行了描述,描述了他的属性与方法。而当这段代码被运行的时候,会有一个工厂来对这段代码进行加工,使之成为一个类对象。这个工厂就是type

type常被用来查看一个变量的类型,但是事实上它的功能远不止于此,type可以说是python中最重要的东西之一。它是最终将class ...这部分代码转换为一个类对象的关键。当然,type自身也是一个类对象。

type就是所有类对象的元类。所有的类对象的创建过程都由它来完成。而type的子类也可以设置为元类。

来看下具体的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def PersonMetaClass(type):
def __new__(cls, name, bases, attrs):
# Do somethings.
pass
return type.__new__(cls, name, bases, attrs)

# python2
class Person:
__metaclass__ = PersonMetaClass

def __init__(self, name, gender):
pass

# python3
class Person(metaclass=PersonMetaClass):
def __init__(self, name, gender):
pass

这里定义了一个元类PersonMetaClass,当Person类对象被创建的时候会调用其元类的__new__方法来完成实际的创建过程。在不指定自定义元类的情况下就会调用type来完成这个过程,而我们自定义的元类则重载了type.__new__方法,以此来加入我们自己所需要的代码。当然在自定义的元类中千万不要忘记了调用type.__new__方法,因为我们重载了方法之后就覆盖了父类的相应方法,如果没有手动调用的话,那么就不会进行类对象的实际创建过程了。

在python2与3中指定元类的语法稍有变化,代码中也给出了两个版本中相应的语法。

元类__new__方法中接收到的参数含义如下:

参数 含义
cls 当前准备创建的类对象
name 类对象的名字
bases 类对象继承的父类集合
attrs 类对象的方法集合

关于元类概念、用法的介绍至此也就算结尾了。

简单总结下,元类就是创建类对象的一个工厂,类对象的创建过程在此完成。

元类的应用

当我们掌握了一个类对象的“生产过程”后,自然可以玩出很多花样来,也可以完成很多原先看起来不可能完成的任务。

比如ORM就是就是元类的一个非常经典的应用。去读一下任意一个python ORM框架的源码,搜索MetaClass都一定护找到让人满意的答案。

对于元类的应用这里就不做过多介绍了,如果有兴趣可以自行搜索了解。

总结

尽管自我感觉理解什么是元类以及其工作的原理,在平时的工作中时常应用到。但当开始写这篇文章的时候,还是感觉力不从心。当把自己的想法落到字面上的时候才发现自己对元类的认知是如此浅薄。想把元类写的透彻一些,却发现有那么多细节是自己不了解的,因此在很多地方对于一些关键概念避而不谈,文章难免显得有些凌乱,还请各位读者见谅。除此之外文章的长度也远远的超出了自己的预料,到最后写道应用的时候已经有心无力了,难以为继所以也就戛然而止了。

文章中还是存在一些错漏,今后会进行修补更正,也会对一些细节增加描述。

果然写博客是程序员提升自身的一个利器啊。写出来的过程本身就是一个自我总结、审查的过程。

最后还是希望这篇文章可以帮到一些初入python的新手。