元类

学懂元类,你只需要知道两句话:

  • 道生一,一生二,二生三,三生万物
  • 我是谁?我从哪来里?我要到哪里去?

在 python 世界,拥有一个永恒的道,那就是 type,请记在脑海中,type 就是道。如此广袤无垠的 python 生态圈,都是由 type 产生出来的。

道生一,一生二,二生三,三生万物。

  • 道 即是 type
  • 一 即是 metaclass(元类,或者叫类生成器)
  • 二 即是 class(类,或者叫实例生成器)
  • 三 即是 instance(实例)
  • 万物 即是 实例的各种属性与方法,我们平常使用 python 时,调用的就是它们。

道和一,是我们今天讨论的命题,而二、三、和万物,则是我们常常使用的类、实例、属性和方法,用 hello world 来举例:

# 创建一个 Hello 类,拥有属性 say_hello ---- 二的起源
class Hello():
    def say_hello(self, name='world'):
        print('Hello, %s.' % name)
 
# 从 Hello 类创建一个实例 hello ---- 二生三
hello = Hello()
 
# 使用 hello 调用方法 say_hello ----三生万物
hello.say_hello()

这就是一个标准的“二生三,三生万物”过程。 从类到我们可以调用的方法,用了这两步。

那我们不由自主要问,类从何而来呢?我们回到代码的第一行。

class Hello 其实是一个函数的“语义化简称”,只为了让代码更浅显易懂,上一篇我们知道可以直接用 type() 创建类:

def fn(self, name='world'): # 先创建一个函数叫 fn
    print('Hello, %s.' % name)

# 通过 type 创建 Hello class ---- 直接从“道”生出了“二”
Hello = type('Hello', (object,), dict(say_hello=fn)) 

这样的写法,就和之前的 Class Hello 写法作用完全相同。

我们回头看一眼最精彩的地方,道直接生出了二:

Hello = type(‘Hello’, (object,), dict(say_hello=fn))

这就是“道”,python 世界的起源,你可以为此而惊叹。

注意它的三个参数!暗合人类的三大永恒命题:我是谁,我从哪里来,我要到哪里去。

  • 第一个参数:我是谁。 在这里,我需要一个区分于其它一切的命名,以上的实例将我命名为 Hello

  • 第二个参数:我从哪里来
    在这里,我需要知道从哪里来,也就是我的“父类”,以上实例中我的父类是 object —— python 中一种非常初级的类。

  • 第三个参数:我要到哪里去
    在这里,我们将需要调用的方法和属性包含到一个 字典 里,再作为参数传入。以上实例中,我们有一个 say_hello 方法包装进了字典中。

值得注意的是,三大永恒命题,是一切类,一切实例,甚至一切实例属性与方法都具有的。

理所应当,它们的“创造者”,道和一,即 type 和元类,也具有这三个参数。但平常,类的三大永恒命题并不作为参数传入,而是以如下方式传入:

# class 后声明“我是谁”
# 小括号内声明“我来自哪里”
# 剩余声明“我要到哪里去”
class Hello(object):
    def say_hello():
        pass

造物主,可以直接创造单个的人,但这是一件苦役。造物主会先创造“人”这一物种,再批量创造具体的个人。并将三大永恒命题,一直传递下去。

“道”可以直接生出“二”,但它会先生出“一”,再批量地制造“二”。

type 可以直接生成类(class),但也可以先生成元类(metaclass),再使用元类批量定制类(class)。




元类 —— 道生一,一生二

一般来说,元类均被命名后缀为 Metalass。想象一下,我们需要一个可以自动打招呼的元类,它里面的类方法呢,有时需要 say_Hello,有时需要 say_Hi,有时又需要 say_Sayolala,有时需要 say_Nihao

如果每个内置的 say_xxx 都需要在类里面声明一次,那将是多么可怕的苦役! 不如使用元类来解决问题。

以下是创建一个专门“打招呼”用的元类代码:

class SayMetaClass(type):
 
    def __new__(cls, name, bases, attrs):
        attrs['say_' + name] = lambda self, value, saying = name : print(saying + ', ' + value)
        return type.__new__(cls, name, bases, attrs)
  • 元类是由 type 衍生而出,所以父类需要传入type。【道生一,所以一必须包含道】

  • 元类的操作都在 __new__ 中完成,它的第一个参数(cls)是将创建的类,之后的参数即是三大永恒命题:我是谁,我从哪里来,我将到哪里去。 它返回的对象也是三大永恒命题,接下来,这三个参数将一直陪伴我们。

我们看看在 __new__() 中,进行的一个操作:

attrs['say_' + name] = lambda self, value, saying = name : print(saying + ', ' + value)

解释下上面的代码:

attrs 本身是一个空字典,name 是我们创建类时候的类名,它跟据类的名字,创建了一个类方法。比如我们由元类创建的类叫 Hello,那创建时就自动有了一个叫 say_Hello 的类方法,然后这个方法名是字典的 key

然后看等号右边的部分,用了一个 lambda 方法把函数本身作为了字典的 value,类的名字 Hello 作为默认参数 saying,传到了方法里面。然后把 hello 方法调用时的传参作为 value 传进去,最终打印出来。

那么,一个元类是怎么从创建到调用的呢?

来!一起根据道生一、一生二、二生三、三生万物的准则,走进元类的生命周期吧!

# 道生一:传入 type
class SayMetaClass(type):
 
    # 传入三大永恒命题:类名、父类、属性/方法
    def __new__(cls, name, bases, attrs):
        # 把属性/方法绑定到 attrs 字典中
        attrs['say_' + name] = lambda self, value, saying = name : print(saying + ', ' + value)
        # 传承三大永恒命题:类名称、父类、属性/方法
        return type.__new__(cls, name, bases, attrs)


class Hello(object, metaclass = SayMetaClass):
    pass

hello = Hello()
hello.say_Hello('world')


class GoodBye(object, metaclass = SayMetaClass):
    pass

goodbye = GoodBye()
goodbye.say_GoodBye('world')

输出:

>>> 
Hello, world
GoodBye, world

注意:通过元类创建的类,第一个参数是父类,第二个参数是 metaclass

当我们执行

class Hello(object, metaclass = SayMetaClass):
    pass

时候到底发生什么了呢?

首先定义了使用元类 SayMetaClass,类名 Hello 会传递给 SayMetaClass 中的 name 参数,object 会传给 bases 参数,我们没有为新的类添加属性和方法,它将会获得 SayMetaClass 类中 attrs 字典中原有的属性和方法。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容