拖了好一段時間了氏淑,終于有空來看看python中的類機(jī)制了啄寡。內(nèi)容太多档押,感覺有些地方還是模糊的仰猖,先寫一些吧套啤,有錯誤煩請指出帝牡。
1 Python對象模型
1.1 概述
python2.2之前的這里就不考慮了婴洼,從2.2之后python對象分為兩類海蔽,class對象和instance對象
,另外還有個術(shù)語type用來表示“類型”
钥组,當(dāng)然class有時候也表示類型這個概念输硝,比如下面的代碼,我們定義了一個名為A的class對象程梦,它的類型是type点把。并且定義了一個實(shí)例對象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對象單獨(dú)列出來,因?yàn)樗芴貏e挺份,是所有class對象的類型褒翰,這里我們稱之為metaclass。而<type 'object'>則是所有對象的基類。它們兩者之間還有聯(lián)系优训,我們按照is-kind-of
和is-instance-of
來劃分關(guān)系朵你,所有class對象的type都是metaclass對象,即在Python的C實(shí)現(xiàn)中對應(yīng)PyType_Type揣非,即所有class對象都是<type 'type'>的實(shí)例(is-instance-of)抡医。而所有class對象的直接或間接基類都是object,即對應(yīng)Python的C實(shí)現(xiàn)中PyBaseObject_Type(is-kind-of)妆兑,更加具體的關(guān)系參見下圖魂拦。
2 class對象和instance對象
2.1 slot和descriptor
Python中的class對象都是PyTypeObject結(jié)構(gòu)體類型變量,比如type對應(yīng)在C實(shí)現(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時濒生,其實(shí)就是執(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中的屬性解析順序浴井,因?yàn)镻ython不像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則是存儲的實(shí)例變量冀墨。
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í)原理與前一篇分析的函數(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)用實(shí)質(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中定義了get和set的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章靴患。