Python源码剖析笔记7-类机制

拖了好一段时间了,终于有空来看看python中的类机制了。内容太多,感觉有些地方还是模糊的,先写一些吧,有错误烦请指出。

1 Python对象模型

1.1 概述

python2.2之前的这里就不考虑了,从2.2之后python对象分为两类,class对象和instance对象,另外还有个术语type用来表示“类型”,当然class有时候也表示类型这个概念,比如下面的代码,我们定义了一个名为A的class对象,它的类型是type。并且定义了一个实例对象a,它的类型是A。

class A(object):
    pass
a = A()

#测试代码
In [7]: a.__class__
Out[7]: __main__.A

In [8]: type(a)
Out[8]: __main__.A

In [9]: A.__class__
Out[9]: type

In [10]: object.__class__
Out[10]: type

In [12]: A.__bases__
Out[12]: (object,)

In [14]: object.__bases__
Out[14]: ()

In [15]: a.__bases__
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-d614806ca736> in <module>()
----> 1 a.__bases__

AttributeError: 'A' object has no attribute '__bases__'

In [16]: isinstance(a, A)
Out[16]: True

In [17]: isinstance(A, object)
Out[17]: True

In [18]: issubclass(A, object)
Out[18]: True

1.2 Python对象之间关系

如1.1中看到的,我这里将 <type 'type'>这个特殊的class对象单独列出来,因为它很特别,是所有class对象的类型,这里我们称之为metaclass。而<type 'object'>则是所有对象的基类。它们两者之间还有联系,我们按照is-kind-ofis-instance-of来划分关系,所有class对象的type都是metaclass对象,即在Python的C实现中对应PyType_Type,即所有class对象都是<type 'type'>的实例(is-instance-of)。而所有class对象的直接或间接基类都是object,即对应Python的C实现中PyBaseObject_Type(is-kind-of),更加具体的关系参见下图。

对象之间的关系.png

2 class对象和instance对象

2.1 slot和descriptor

Python中的class对象都是PyTypeObject结构体类型变量,比如type对应在C实现中是PyType_Type,int对应则是PyInt_Type。int的类型是type,但是比较特殊的type,它的类型是自己,如下所示。当然它们的基类都是object。

In [2]: type.__class__
Out[2]: type

In [3]: int.__class__
Out[3]: type

In [4]: int.__base__
Out[4]: object

In [5]: type.__base__
Out[5]: object

Python在初始化class对象时会填充tp_dict,这个tp_dict会用来搜索类的方法和属性等。Python会对class对象的一些特殊方法进行特殊处理,这就引出了slot和descriptor的概念,其中对于一些特殊方法比如__repr__,python中会设置一个对应的slot,由于slot本身不是PyObject类型的,所以呢会增加一个封装,也就是descriptor了,最终在一个class对象的tp_dict中,方法名如__repr__会指向一个descriptor对象,而descriptor对象是对slot的封装,slot中会有一个slot function,比如对应__repr__的就是slot_tp_repr方法,__init__指向的是slot_tp_init方法。这样,如果在一个class中重新定义了__repr__方法,则在创建class对象的时候,就会将默认的tp_repr指向的方法替换为该slot_to_repr方法,最终在执行tp_repr时,其实就是执行的slot_to_repr方法,而在slot_to_repr方法中就会搜索并找到该class对象中定义的__repr__方法并调用,这样就完成了方法的复写。

比如下面的代码中class A继承自list,如果没有复写__repr__,则在输出的时候会调用list_repr方法,打印的是'[]',如果如下面这样复写了,则打印的是'Python'

>> class A(list):
    def __repr__(self):
        return 'Python'
>> s = '%s' % A()
>> s
   'Python'

2.2 MRO简析

MRO是指python中的属性解析顺序,因为Python不像Java,Python支持多继承,所以需要设置解析属性的顺序。MRO搜索规则如下:

  • 1)先从当前class出发,比如下面就是先获取D,发现D的mro列表tp_mro没有D,则放入D。
  • 2)获得C,D的mro列表没有C,则加入C。此时,Python虚拟机发现C中存在mro列表,于是转而访问C的mro列表:
    • 2.1)获得A,D的列mro表没有A,则加入A。
    • 2.2)获得list,尽管D的mro列表没有list,但是后面B的mro列表里面有list,于是这里不把list放到D的mro列表,推迟到处理B时放入。
    • 2.3)获得object,同理也推迟再放。
  • 3)获得B,D的mro列表没有B,则放入B。转而访问B的mro列表:
    • 3.1)获得list,将list放入D的mro列表。
    • 3.2)获得object,将object放入D的mro列表。
  • 4)最终,D的mro列表为(D,C,A,B,list,object)。可以打印D.__mro__查看。所以最终输出为A:show.
class A(list):
  def show(self):
    print 'A:show'

class B(list):
  def show(self):
    print 'B:show'

class C(A):
  pass

class D(C, B):
  pass


d = D()
d.show()

2.3 class对象和instance对象的__dict__

观察class对象和instance对象的dict,如下代码可以看到结果,class对象的dict对应的类的属性,而instance对象的dict则是存储的实例变量。

class A(object):
  a = 1
  b = 2

  def __init__(self):
    self.c = 3
    self.d = 4

  def test(self):
    pass

  def __repr__(self):
    return 'A'

a = A()
print A.__dict__
print a.__dict__
print a

##输出结果
{'a': 1, '__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', 'b': 2, '__repr__': <function __repr__ at 0x103eb1e60>, 'test': <function test at 0x103eb1758>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, '__init__': <function __init__ at 0x103eb1140>}
{'c': 3, 'd': 4}
A

2.4 成员函数

调用成员函数时,其实原理与前一篇分析的函数原理基本一致,只是在类中对PyFunctionObject包装了一层,封装成了PyMethodObject对象,这个对象除了PyFunctionObject对象本身,还新增了class对象和成员函数调用的self参数。PyFunctionObject和一个instance对象通过PyMethodObject对象结合在一起的过程就成为成员函数的绑定。成员函数调用时与一般函数调用机制类似,a.f()函数调用实质就是带了一个位置参数(instance对象a)的一般函数调用。

class A(object):
  def f(self):
    pass

a = A()
print A.f # <unbound method A.f>
print a.f # <bound method A.f of <__main__.A object at 0x10d8616d0>>

3 Python属性选择算法

再谈到属性选择算法之前,需要再说明下descriptor。descriptor分为两种,如下:

  • data descriptor: type中定义了getset的descriptor。
  • no data descriptor: type中只定义了get的descriptor。

Python属性选择算法大致规则如下:

  • Python虚拟机按照instance属性和class属性顺序选择属性,instance属性优先级高。
  • 如果在class属性中发现同名的data descriptor,则data descriptor优先级高于instance属性。
#1.data descriptor优先级高于instance属性
class A(list):
  def __get__(self, obj, cls):
    return 'A __get__'

  def __set__(self, obj, value):
    print 'A __set__'
    self.append(value)

class B(object):
  value = A()

b = B()
b.value = 1
print b.value # A.__get__
print b.__class__.__dict__['value'] # [1]
print b.__dict__['value'] # 报错

#2.instance属性优先级高于no data descriptor
class A(list):
  def __get__(self, obj, cls):
    return 'A __get__'

class B(object):
  value = A()

b = B()
b.value = 1
print b.value # 1
print b.__class__.__dict__['value'] # []
print b.__dict__['value'] # 1

4 其他

Python对象原理还有些不甚明了的地方,暂时记录到这里,后续再补充了。笔记来自《python源码剖析》一书的12章。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,111评论 18 139
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,643评论 0 9
  • # 第一优先级规则声明: # 除了梦境,每一个意识主进程都必须与一个身体参与的机械进程相匹配,否则结束意识主进程。...
    李洞BarryLi阅读 3,793评论 0 1
  • —01— 从小,我就是个古怪的孩子,总能提前知道别人不知道的很多事情。 上小学的时候,我就跟别人的烦恼不一样。身边...
    渔晞阅读 916评论 15 13
  • ​往下看之前,我们先说两个概念: 1、皮质醇:当你紧张时,大脑会释放皮质醇。而皮质醇是有毒的,它会使人的思维不清晰...
    朗读者晟焕阅读 908评论 0 2