python對象之屬性訪問流程

爲什麼題目中是python中對象(object)屬性,而不是類(class)屬性呢?

       這涉及到object與class之間的關係。在C++和Java中,object等同於instance,而與class區分開來。但是在python中一切皆object,function、int、float、class、instance等等都是object。所以這裏的‘object’泛指instance、class、以及python自定義的type(如object、int、str等等)。

       據說這些object,在python運行時,都已經加載到內存中。使用id(),查看一下,都返回了一個整數。該整數是對應object在內存中線性地址的十進制表達,所以每一個object都有一個唯一的id。

既然python中一切都是object,那爲什麼還要有class和instance之分呢?

       雖然python中一切都是object,但是根據object本身的用法不同,可將object分爲三類:instance object、type object、metaclass object[1]。

    instance object就是我們一般使用class實例化得到的數據對象,代表具體的數據實例。如:

a=int(1)
c=1.2
b=Myclass()

    type object故名思意就是用作數據抽象的對象,如python自帶的int、float、str等和我們自己定義的一般class。這裏的type跟我們所說的class很接近,但同樣包含python自定義的數據類型。所以也可以稱爲class object:

class Myclass():
    class_attr = "class attribute"
    
    def func(self, a):
        self.instance_attr=a
        print self.class_attr

    metaclass object是一種比較高極,但基本不會用到的對象,包括python自帶的type,以及所有繼承type的class。該類對象的作用是用來構造class(type object)。可以認爲metaclass是一個可調用對象,給它傳入特定的參數就可以生成對應的class[3]。使用metaclass來生成class:

def func(self, a):
    self.instance_attr = a
    print self.class_attr

Myclass = type("Myclass", (), {"class_attr": "class attribute", "func": func})

三種object的關係如下圖所示(實線代表繼承關係,虛線代表實例化):

The Python Objects Map

上圖中,可以分析出,在python中,生成一個對象有兩種方法:

1 繼承(subclass),使用class關鍵字來定義。通過這個方法可以生成metaclass和type object

2 實例化(initialization),使用type object進行初始化,如a=int(1)

對象屬性的訪問流程

       上面對python的object做了一個簡要的介紹,瞭解下object與object之間的關係。當通過a=object.attribute去訪問對象屬性時,返回是object中屬性值,還是它所屬class的屬性值,或者是它所屬class的基類的屬性值?       python的對象屬性訪問,可以分爲如下幾個步驟。無論該對象是class還是instance,都遵守如下流程:

首先,搜索object中除__dict__外的所有屬性,如果有匹配的則直接返回該屬性(這裏應該都是python自己定義的一些屬性,如__class__、__doc__);

其次,搜索object.__class__.__dict__,如果存在且該屬性爲data descriptor,則返回該descriptor的執行結果。沒有則繼續搜索object.__class__.__bases__中所有基類的__dict__,依次遞歸;

再次,搜索object.__dict__,如果該屬性存在併爲descriptor,且object爲class,則執行該descriptor的執行結果,否則直接返回該屬性值。如果object爲class且__dict__中不存在該屬性,則將繼續搜索其__bases__;

第四,搜索object.__class__.__dict__,如果屬性存在,且爲non-data descriptor(注意由於第二步對data descriptor做過處理,所以這裏不可能爲data descriptor),則返回該descriptor的執行結果,否則直接返回該屬性值;

最後,上面都沒有找到,則會拋出AttributeError異常。

對象屬性的設置流程

       當通過object.attribute=a來設置對象屬性時,流程如下:

首先,搜索object.__class.__dict__是否存在,且該屬性爲data descriptor,如果存在,則執行該descriptor的__set__(),如不存在依次尋找其基類,依次遞歸;

其次,如果上面沒有找到,則直接向object.__dict__賦值結果。如果object爲class且該屬性爲data descriptor,則調用該descriptor的__set__函數。如果object爲class,且它的__dict__及基類的__dict__都不包含該屬性的data descriptor,則直接賦值於object.__dict__中,而不管基類的__dict__是否也存在該屬性。存在一種特殊情況,當object.__class__有屬性__slots__(一個list)時,會判斷"attribute"是否在__slots__中,如果不在,則無法在object.__dict__中插入結果。

對象屬性的刪除流程與設置流程基本一致。

類繼承與super的使用

       在類繼承中,子類可能會重新實現父類的某些方法,並在實現中調用父類的方法,如下所示

class A1(object):
    def func(self,str)
        print 'class A1',str

class B1(A1):
    def func(self, str)
        A.func(self, str) #注意此處調用的func是A的 unbound mothod,需要傳遞self給它
        print 'class B1', str

b = B1()
b.func('hello')
運行該腳本,輸出如下:

class A1 hello
class B1 hello

      這種繼承由於每一個子類只有唯一的一個父類,所以通過A.func(self, str)調用父類方法不會存在什麼問題。但是如果存在下面情況呢:

class C1(A1):                                                                     
    def func(self, str):                                                        
        A1.func(self,str)                                                       
        print 'class C1', str                                                     
                                                                                  
class D1(B1, C1):                                                                 
    def func(self, str):                                                          
        B1.func(self,str)                                                         
        C1.func(self,str)                                                         
        print 'class D1', str 
d1 = D1()
d1.func()
輸出結果如下:

class A1 hello
class B1 hello
class A1 hello
class C1 hello
class D1 hello
      從上面可以看出A1的func被調用了兩次,但這不是我們想要的,有時這很可能導致出現問題。使用super來調用父類的方法,可以避免出現上述問題。代碼如下:

class A(object):                                                                
    def func(self, str):                                                        
        print 'class A', str                                                    
                                                                                
                                                                                
class B(A):                                                                     
    def func(self,str):                                                         
        super(B, self).func(str)                                                
        print 'class B', str                                                    
                                                                                
                                                                                
class C(A):                                                                     
    def func(self, str):                                                        
        super(C, self).func(str)                                                
        print 'class C', str                                                    
                                                                                
                                                                                
class D(B,C):                                                                   
    def func(self,str):                                                         
        super(D, self).func(str)                                                
        print 'class D', str                                                    
                                                                                
d=D()                                                                           
d.func('hello')

輸出結果如下:

class A hello
class C hello
class B hello
class D hello

注意在D中,只調用了一條super(D,self).func(str),並沒有一一去調用父類的func。但能調用它所有父類的func。而且保證每一父類的函數只執行一遍。

python在每一個class中都添加一個__mro__屬性,這個屬性存放了該類和所有基類,並按繼承順序存放。如下:

A.__mro__
(A, object)
B.__mro__
(B, A, object)
C.__mro__
(C, A, object)
D.__mro__
(D, B, C, A, object)
       當我們執行super(D, self).func(str)時,super首先獲取self.__class__.__mro__的值,然後在列表中尋找元素D後面的元素(B),然後調用B.func(self, str)。在B.func中又會執行super(B,self),func(str)。這樣它會在__mro__中尋找元素B後面的元素C,接着調用C.func(self,str)。按如此順序調用所有基類的func函數,不會出現基類函數重複調用的問題。所以在編寫代碼時,需儘量使用super,而不能使用基類進行直接調用。


參考文檔:

[1]http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html

[2]http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html

[3]http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

[4]http://python-history.blogspot.ru/2010/06/method-resolution-order.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章