Python源碼剖析筆記7-類機(jī)制

拖了好一段時間了,終于有空來看看python中的類機(jī)制了。內(nèi)容太多,感覺有些地方還是模糊的,先寫一些吧,有錯誤煩請指出。

1 Python對象模型

1.1 概述

python2.2之前的這里就不考慮了,從2.2之后python對象分為兩類,class對象和instance對象,另外還有個術(shù)語type用來表示“類型”,當(dāng)然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對象之間關(guān)系

如1.1中看到的,我這里將 <type 'type'>這個特殊的class對象單獨列出來,因為它很特別,是所有class對象的類型,這里我們稱之為metaclass。而<type 'object'>則是所有對象的基類。它們兩者之間還有聯(lián)系,我們按照is-kind-ofis-instance-of來劃分關(guān)系,所有class對象的type都是metaclass對象,即在Python的C實現(xiàn)中對應(yīng)PyType_Type,即所有class對象都是<type 'type'>的實例(is-instance-of)。而所有class對象的直接或間接基類都是object,即對應(yīng)Python的C實現(xiàn)中PyBaseObject_Type(is-kind-of),更加具體的關(guān)系參見下圖。

對象之間的關(guān)系.png

2 class對象和instance對象

2.1 slot和descriptor

Python中的class對象都是PyTypeObject結(jié)構(gòu)體類型變量,比如type對應(yīng)在C實現(xiàn)中是PyType_Type,int對應(yīng)則是PyInt_Type。int的類型是type,但是比較特殊的type,它的類型是自己,如下所示。當(dāng)然它們的基類都是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對象的一些特殊方法進(jìn)行特殊處理,這就引出了slot和descriptor的概念,其中對于一些特殊方法比如__repr__,python中會設(shè)置一個對應(yīng)的slot,由于slot本身不是PyObject類型的,所以呢會增加一個封裝,也就是descriptor了,最終在一個class對象的tp_dict中,方法名如__repr__會指向一個descriptor對象,而descriptor對象是對slot的封裝,slot中會有一個slot function,比如對應(yīng)__repr__的就是slot_tp_repr方法,__init__指向的是slot_tp_init方法。這樣,如果在一個class中重新定義了__repr__方法,則在創(chuàng)建class對象的時候,就會將默認(rèn)的tp_repr指向的方法替換為該slot_to_repr方法,最終在執(zhí)行tp_repr時,其實就是執(zhí)行的slot_to_repr方法,而在slot_to_repr方法中就會搜索并找到該class對象中定義的__repr__方法并調(diào)用,這樣就完成了方法的復(fù)寫。

比如下面的代碼中class A繼承自list,如果沒有復(fù)寫__repr__,則在輸出的時候會調(diào)用list_repr方法,打印的是'[]',如果如下面這樣復(fù)寫了,則打印的是'Python'

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

2.2 MRO簡析

MRO是指python中的屬性解析順序,因為Python不像Java,Python支持多繼承,所以需要設(shè)置解析屬性的順序。MRO搜索規(guī)則如下:

  • 1)先從當(dāng)前class出發(fā),比如下面就是先獲取D,發(fā)現(xiàn)D的mro列表tp_mro沒有D,則放入D。
  • 2)獲得C,D的mro列表沒有C,則加入C。此時,Python虛擬機(jī)發(fā)現(xiàn)C中存在mro列表,于是轉(zhuǎn)而訪問C的mro列表:
    • 2.1)獲得A,D的列mro表沒有A,則加入A。
    • 2.2)獲得list,盡管D的mro列表沒有l(wèi)ist,但是后面B的mro列表里面有l(wèi)ist,于是這里不把list放到D的mro列表,推遲到處理B時放入。
    • 2.3)獲得object,同理也推遲再放。
  • 3)獲得B,D的mro列表沒有B,則放入B。轉(zhuǎn)而訪問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,如下代碼可以看到結(jié)果,class對象的dict對應(yīng)的類的屬性,而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

##輸出結(jié)果
{'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 成員函數(shù)

調(diào)用成員函數(shù)時,其實原理與前一篇分析的函數(shù)原理基本一致,只是在類中對PyFunctionObject包裝了一層,封裝成了PyMethodObject對象,這個對象除了PyFunctionObject對象本身,還新增了class對象和成員函數(shù)調(diào)用的self參數(shù)。PyFunctionObject和一個instance對象通過PyMethodObject對象結(jié)合在一起的過程就成為成員函數(shù)的綁定。成員函數(shù)調(diào)用時與一般函數(shù)調(diào)用機(jī)制類似,a.f()函數(shù)調(diào)用實質(zhì)就是帶了一個位置參數(shù)(instance對象a)的一般函數(shù)調(diào)用。

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屬性選擇算法大致規(guī)則如下:

  • Python虛擬機(jī)按照instance屬性和class屬性順序選擇屬性,instance屬性優(yōu)先級高。
  • 如果在class屬性中發(fā)現(xiàn)同名的data descriptor,則data descriptor優(yōu)先級高于instance屬性。
#1.data descriptor優(yōu)先級高于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屬性優(yōu)先級高于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對象原理還有些不甚明了的地方,暫時記錄到這里,后續(xù)再補(bǔ)充了。筆記來自《python源碼剖析》一書的12章。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,767評論 0 9
  • # 第一優(yōu)先級規(guī)則聲明: # 除了夢境,每一個意識主進(jìn)程都必須與一個身體參與的機(jī)械進(jìn)程相匹配,否則結(jié)束意識主進(jìn)程。...
    李洞BarryLi閱讀 3,904評論 0 1
  • —01— 從小,我就是個古怪的孩子,總能提前知道別人不知道的很多事情。 上小學(xué)的時候,我就跟別人的煩惱不一樣。身邊...
    漁晞閱讀 952評論 15 13
  • ?往下看之前,我們先說兩個概念: 1、皮質(zhì)醇:當(dāng)你緊張時,大腦會釋放皮質(zhì)醇。而皮質(zhì)醇是有毒的,它會使人的思維不清晰...
    朗讀者晟煥閱讀 993評論 0 2