Python中类创建的过程以及元类的理解

首先类是一个什么东西

类是数据与操作其数据方法的封装

一个定义python类的例子:

class B:
    def rename(self, newname):
        self.name = newname
class A(B):   # 继承B,能用B的公有属性和方法
    name = 'A'     # 公有属性
    def __init__(self):
        self.name = 'a'  # 私有属性
    def get_name(self):  # 方法(公有)
         return self.name
a = A()
print(a.get_name())   # a
a.rename('b')
print(a.get_name())   # b

于此,能解决python编程中90%以上的应用
python中类的创建与其实例化如此简单,背后却是因为底层做好了足够的封装,那么究竟在python里面类是种什么样的东西呢?

一步步来...

在python中:

处处都是对象

因此我们上面实例出的a是一个对象,定义的类A、B也是对象,相信看到此处很多初学者都是一脸懵逼的,那么它怎么是一个对象呢?

先来介绍python中的对象, 避免复杂化问题,这里简化出对象实际上是占用内存中的一个东西:

内存中对象.png

python的解析器是用c写的,那么,在c中描述这个对象的话,用的是一个c结构体,而这个结构体里面,不止上面对象那么简单,而是:


复杂一点点的对象.png

在这个对象(结构体)里面,有一个fn的东西叫做引用计数,这个暂时放一放,先来讨论class这个变量,这个变量说明(指向)了一个xx(暂时未知)的类型,在python设计的''游戏规则''里面,此时就能够告诉我们,这一个对象是一个xx类型的对象,既然在python里面处处都是对象,那么这个对象指向类型,也应该是一个对象,如图:


对象的类型.png

类型(对象2)也是一个对象,但是对象2的类型是谁呢?总不可能这样这样嵌套下去吧,对的,python的设计开始要把这个玩法''圆''起来了,那么是什么呢?

type

对象2的类型就是type,'自圆其说'的终点,这个type是python底层封装好的一个类型(结构体),按照设计的一致,type对象里面也有类型指向,那么怎么终止嵌套呢?type的类型指向了它自己.....嵌套结束....那么这个'游戏'的规则是这样的:


类型链.png

一个小验证:

class A:
    pass
a = A()
print(a.__class__)         # <class '__main__.A'>
print(A.__class__)         # <class 'type'>
print(type.__class__)     # <class 'type'>

也就是说a的类型是A,A的类型是type,type的类型还是type

感觉越跑越偏了,python中的类创建和元类跟这些'游戏规则'有什么联系?现在也没涉及到'创建'这个概念啊,那是因为还缺少一个东西,让'游戏'拥有'创建'的功能...

object

缺少的东西就是这个----object,我们知道,在python2.x的时候看见一些代码在写类的时候必须继承一个object,如下:

class human(object):
    def run(self):
        print 'i am running...'

就连type中也要....

class type(object):
    ...

那么这个object很有可能就是拥有创建一切能力的东西,类都要继承于它(暂时看来),当然,python底层也封装好了这个object类对象,那么它应该也有对应指向的类型啊,它的类型是什么呢?

print(object.__class__)
# <class 'type'>

......绕晕了,object的类型竟然是type,也就是说type要继承object,而object的类型是type,object不继承任何东西(继承的顶端)

果不其然,用起来简单的东西,里面的设计就是这么复杂且绕.....

先介绍类'玩法'的两个概念性的东西:
实例化:由类实例化出来对象的一个功能
继承: 子类继承于父类,也就是子类可以用父类的公有属性和方法

type和object就是为了满足这个游戏规则而设计出来的产物,都是为了结束嵌套而早就封装定义好的两个结构体(对象)
type: 我是实例化的顶端
object: 我是继承的顶端

纵观这个python设计里面,加上object之后就是这样的了:


关系.png

从图中看,现在的游戏规则就是:
所有'类型链'的顶端是type 。所有'继承链'的顶端是object

类型是能够'提供'给使用者创建出实例的功能,图中的type和对象2都需要有这样的一个功能,在图中看出他们都继承自object,那么根据继承的玩法,如果object中有这样的一个'创建出'实例的功能,那就皆大欢喜了。

没错,object就是有这样的功能------在内存中建立'对象'

也就是图中的黑色框那玩意,是object._new_()出来的,被object._new_()出来之后也只是一个空壳,并没有我们想要定义的东西啊例如自定义属性、方法之类的,当然,在object创建出的空框之后,就会调用我们定义类的_new_方法,而我们常用的_init_方法则是之后才调用,保证让你想怎么样就怎么样

实例一个类的过程

class A(object):
    # 我们自定义这个类的__new__方法
    def __new__(cls, *args, **kwargs):
        print('创建的时候用父类object的__new__方法获得一个实例(空)')
        instance = super().__new__(cls, *args, **kwargs)
        print('在此自定义实例化后增加的东西')
        return instance
    def __init__(self, *args, **kwargs):
        print('最后用到自定义的__init__')
        pass
a = A()
得出:
# 创建的时候用父类object的__new__方法获得一个实例(空)
# 在此自定义实例化后增加的东西
# 最后用到自定义的__init__

实例化调用链:
my_class._new_() ------> 父类(object)._new_() ------> instance(实例)

此时解决了我们正常使用的时候定义的class xxx实例化的过程,但是,我们定义的类也是一个对象啊,也需要它的类型进行实例化这样的一个过程,上面讲到,类对象的类型是type,人家封装好了,我们还怎么进去分析甚至是用它? 想要它的能力的话,那就继承它吧....也是接下来说的----元类...

元类metaclass

概念不难懂,就是上述定义的类型A的类型, 简称类的类(很绕),也就是元类...,要使用元类的话必须要有type的'功能',那我们就继承它吧....

class my_meta(type):
    pass

咋一看又是一个class......看到此处也是懵逼的,那么先把疑问抛弃,看一下这玩意能够实例出什么来,运用上面类进行实例的过程!

调用my_meta._new_()方法的时候,找父类(继承于type),父类是type,就是要调用type._new_(),type也有父类啊(继承于object),又调用object._new_()去创建一个实例空壳,回来之后这个实例空壳回到了type的_new_方法中,由于type被设计者封装了,里面就是把这个实例'赋予'类属性和方法,也就是最上面图中对象2里面的属性跟方法。

class my_meta(type):
    # 这个实例过程需要传入3个参数
    # 元类,类名, 继承, 属性方法
    def __new__(mcls,  name, bases, attrs):
        # 调用type的.__new__方法,其中调用object生成空壳
        # 对该空壳根据传入type中的参数mcls, name, bases, attrs进行填补进去 
        # cls = super().__new__(mcls, name, bases, attrs)
        cls = type.__new__(mcls, name, bases, attrs)
        return cls
my_class = my_meta('my_class', (), {'name':'my_class'})
print(my_class.name)
# my_class拥有实例功能(证明我是一个类)
m = my_class()
print(m.name)
# my_class
# my_class

我们由自定义my_meta类继承于type,实例出来的就像我们平时定义的类,也就是说,我们现在可以动态控制类的生成。
更通用的写法:

class my_meta(type):
    # 这个实例过程需要传入3个参数
    # 元类,类名, 继承, 属性方法
    def __new__(mcls,  name, bases, attrs):
        # 调用type的.__new__方法,其中调用object生成空壳
        # 对该空壳根据传入type中的参数mcls, name, bases, attrs进行填补进去 
        # cls = super().__new__(mcls, name, bases, attrs)
        print('改你想改的(传入参数修改)!')
        cls = type.__new__(mcls, name, bases, attrs)
        print('改你想改的(返回之后的修改)!')
        return cls

# 显式传入metaclass参数,否则仍然把这个类对象用type实例出来
class A(metaclass=my_meta):
    # 程序这里的名字A,该类继承于xx,类方法__new__和__init__和speak
    # 将会传入my_meta中作为参数(name, bases, attrs)
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)
    def __init__(self, *args, **kwargs):
        pass
    def speak(self):
        print('speaking..')

# 改你想改的(传入参数修改)!
# 改你想改的(返回之后的修改)!

最后的输出就说明了我们写好了这个类时候,会经过my_meta的创建过程!

解决一些坑

1、有人会问,我们不是还写了class my_meta吗,那它这是谁创建的啊?
根据游戏规则,my_meta的类是谁?查找到父类type,它的类是type自身,new出这个my_meta就是调用了type的new再往上object的new,而type是底层封装好的,终止嵌套。。。

2、实例化的时候,调用的是类加括号进行实例化(A()),是怎么回事?
当调用A()的时候,python会从A的类中查找其_call_方法,当A的类为type的时候,也就是调用type._call_(A, *args, **kwargs),里面调用A._new_(A, *args, **kwargs),同上进行实例化过程...

也就是为什么我们在定义一个类的时候,定义了其_call_方法就可以是其类的实例拥有'可调用'的功能:

class A:
    def __init__(self):
        self.name = 'a'
    def __call__(self, sth):
        return self.name + ' speak ' + sth 

a = A()
print(a('hello'))

# a speak hello

这就是同理在type中控制了其实例出来的东西拥有'可调用'的功能,并且调用之后是实例化该类,故此我们实例一个类的时候直接A()即可,伪代码:

class type:
    def __new__(...):
        ...
    def __call__(cls, *args, **kwargs):
        # 调用其__new__方法
        instance = cls.__new__(cls, *args, **kwargs)
        # 调用其__init__方法进行初始化
        instance.__init__(*args, **kwargs)
        return instance

3、type和object这样的鸡生蛋蛋生鸡的关系,究竟为什么要这么绕?
type和object都是为了python这样的对象系统而存在的,至于为什么要这么设计,只能说,游戏规则由设计者定...javascript甚至其他语言的对象系统都有自己的一套方法,type和object正是为了给这个游戏提供支持而已。

最后

这里只是显浅地对python中类创建过程和元类的理解,并不完全严谨,要理解其语言的强大之处还需要懂c语言并且阅读其解析器的源代码

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

推荐阅读更多精彩内容

  • 包(lib)、模块(module) 在Python中,存在包和模块两个常见概念。 模块:编写Python代码的py...
    清清子衿木子水心阅读 3,798评论 0 27
  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 4,207评论 4 16
  • 我是一个地地道道的北方姑娘,今年5月正式成为一名四川媳妇,说起这之中的感受,可谓如人饮水冷暖自知。 还记得第一次去...
    夏目彩虹阅读 519评论 0 0
  • 他们是发小。 他总会像个孩子似的做错事,道歉,以至于父母都在抱怨。 她完全是榜样,如爱因斯坦般优秀,无时不刻被师长...
    TheMy_cf23阅读 194评论 0 4