谈谈python中的元类(meta-class)

如题所述

近期又要开始忙碌的生活了,论文阅读只能暂时搁置,来科普一下编程语言的知识,并把它阐述清楚,也算是对知识的一些整理工作。

本文旨在理解元类的基本概念,理解元类在定制类时的运行顺序,以及元类的一些用途。

一、元类的基本概念

“类”这个概念相信有编程基础的朋友们都理解。现在主要解释“元”这个词。 “元”这个词是翻译自英文中的“meta",比如近年来很火的”元学习“(meta-learning")。这个概念不太好用官方的语言来解释,只能用类比的方式先有一个初步的理解(水平有限):

1、”元“ 表示某个物体的一些基本信息,这些信息可以用于很多不同的场景。

比如一个文件的权限,创建时间,修改时间等;

又比如一个人,无论在什么场景下,基本的吃饭,刷牙以及睡觉等”元行为”是不需要重新学习的,稍微适应一下新的场景就可以了;

再比如元学习,如果让一个模型学到的东西是“元信息”,比如一个物体的颜色,形状,大小等,它就有可能只需要针对某个特定的分类场景做微调,就能够很好地完成特定的学习任务。

2、“元”表示一个通用,灵活的模板或规则,用于定制其他具有特定功能,又要受到规则约束的函数或者类。

比如在定义某个函数时,要求它的参数满足类型的约束;

比如在定义某个类时,要求它必须具有某些属性。

上面这两句话可能比较抽象,结合后面给出的代码示例可以更好地理解(show me the code)。

结合上面两个特点,元类首先也是一个类,不过它的作用是对继承它的类的行为进行约束,也就是说,它制定了规则,要求继承它的类在满足这个规则基础上,再实现其他细分的功能。

二、元类在定制类时的构建顺序

本节是关键中的关键,相当于是九阳神功易筋经之类的内功心法,打好了这个基础,才能发挥出更好的武功招式来使用元类。

python中元类的特点:

1、继承自type

type在python中是个比较神奇的东西,python中任何内置类或者自定义的类都是type的实例(注意是实例,instance)。而object是python中任何类的父类,它也是type的实例,同时type的父类也是object。(有点像鸡和蛋的关系,这不是本文重点,不深究)

2、定义了定制类相关的函数

假设现在有元类A,继承了元类A的类B。

与定制类相关的函数主要有(不一定全要有):

__prepare__():当元类A中定义了这个函数,它将运行于类B的定义体构造之前,为类B创建一个dict,存储类B的类属性,函数名等信息;

__new__():当元类A中定义了这个函数,它将运行于类B的定义体构造之后,通过__prepare__()中构建的dict来访问或控制类B的类属性或函数名;(比如在这里就可以限制类B的函数名必须全部为小写)

__init__():这个函数在定义普通类的时候经常用,不过在普通类中,它实际上是初始化函数,真正构造出实例的是__new__()函数。当元类A中定义了这个函数,它将运行于__new__()函数之后,常用于定制类B的子类的行为。

综上所述(!!!划重点!!!),在定义了元类A以后,继承了元类A的类B的构造顺序为:

__prepare__()函数调用 ---> 类B的类属性与函数定义 ---> __new__()函数调用 ---> __init__()函数调用 ---> 类B的实例化完成。

请仔细结合每个函数的功能说明,把上面这个顺序走一遍,可以留下一个关于元类的初步印象。

当然,如果是第一次接触,上面的功能说明还是很抽象,没关系,后面还有代码示例。

三、元类的一些用途

内功心法已经在上一节修炼完了,现在要通过实战来学习武功招式,把内功发挥出来。

这一节需要点耐心,初次接触可能不是在公交或者地铁上看看就能理解的。

使用元类对类的属性进行约束

1、定义工具函数 首先使用inspect库来定义一个小工具函数make_sig,这个函数的功能就是获取一个可迭代对象(如list),可迭代对象的内容是待构建类的属性名,并返回一个签名对象Signature:

2、创建元类

注意创建元类的特点,继承自type,定义与定制类有关的函数。

3、创建元类的继承类 有了元类以后,就需要有一个继承类,来使用元类的定制规则。

到这里就可以结合具体的代码,讲讲运行的顺序了:

(1) 运行StructureMeta.__prepare__() : 准备好一个OrderedDict,存储继承类的类属性;

(2)Structure._fields, Structure.__init__()定义好,并存放在OrderedDict中;

(3)运行StructureMeta.__new__():接收OrderedDict作为参数,并增加__signature__类属性给继承类Structure;

(4)运行StructureMeta.__init__():注意元类的__init__()函数参数self代表的是继承类Structure,而不是继承类的实例

此时运行代码,可以看到以下结果(建议手动运行一遍):

执行顺序和上面所描述的一模一样:

先进入__prepare__()函数,然后执行继承类Structure的类定义体,然后执行__new__()

,可以看到clsdict就是OrderedDict,并且可以看到_fields这个类属性已经存在了,最后执行在__init__(),此时已经可以看到__signature__这个类属性的存在了。

4、用起来!

使用就非常简单了:

定义一个股票类,它必须包含股票名,股份数和价格的类属性;

定义一个2维坐标类,必须包含x和y坐标属性:

再来看看运行结果:

可以看到,仍然是按照上面描述过的执行顺序来对定制类进行构建,这里可以清楚地看到_fields和__signature__中的内容分别是什么。

总结 1、元类的功能可以用来定义一些规则或者模板,让继承它的类在满足这些规则的基础上完成其他的细分功能; 2、元类及其继承类的构建顺序:元类__prepare__() ---> 继承类定义体 ---> 元类__new__() ---> 元类__init__();

好了,恭喜看到最后的各位读者,在《fluent python》这本书中提到,“90%以上的python程序员不需要用到元类”。但了解了解终归不是坏事,磨刀不误砍柴工,读完博士再打工。
温馨提示:答案为网友推荐,仅供参考
相似回答
大家正在搜