1. 程式人生 > >Pyhon 中類屬性與例項屬性的區別

Pyhon 中類屬性與例項屬性的區別

根據網上的一些部落格和自己的理解,說一下自己的想法,某些地方加了一些自己的理解,某些地方直接摘抄他人的,具體可以直接看例子,如有不準確非常希望大家指正,如有表達不好請大家見諒。

情景一:

# -*- coding: utf-8 -*-
class A(object):
    t = []
    t1 = 0    
    def add(self, c):
        print c,'int id',id(self.t),c,'list id', id(self.t1)
        self.t1 += 1        
        self.t.append(1)
        print
c,'int id',id(self.t),c,'list id', id(self.t1) print self.t1, self.t def sss(self, c): print self.t1, self.t a = A() a1 = a.add('a') print id(a1) a2 = a.add('a') print id(a2) a.add('a') a.sss('a') b = A() b1 = b.add('b') print id(b1) b.add('b’) b.sss('b') a.sss('a')

結果:

a int
id 4377846200 a list id 140559058517120 a int id 4377846200 a list id 140559058517096 1 [1] 4410593176 a int id 4377846200 a list id 140559058517096 a int id 4377846200 a list id 140559058517072 2 [1, 1] 4410593176 a int id 4377846200 a list id 140559058517072 a int id 4377846200 a list id 140559058517048 3 [1, 1, 1] 3 [1, 1, 1] b int
id 4377846200 b list id 140559058517120 b int id 4377846200 b list id 140559058517096 1 [1, 1, 1, 1] b int id 4377846200 b list id 140559058517096 b int id 4377846200 b list id 140559058517072 2 [1, 1, 1, 1, 1] 2 [1, 1, 1, 1, 1] 3 [1, 1, 1, 1, 1]

情景二:

class Test():
    test = 10
# 情形1
obj1 = Test()
obj2 = Test()
print obj1.test, obj2.test, Test.test
# 情形2
obj1.test += 2
print obj1.test, obj2.test, Test.test
# 情形3
Test.test += 3
print obj1.test, obj2.test, Test.test
# 情形4
obj2.test += 2
print obj1.test, obj2.test, Test.test
# 情形5
Test.test += 3
print obj1.test, obj2.test, Test.test

結果:

10 10 10
12 10 10
12 13 13
12 15 13
12 15 16

首先看 情景一:
原因是因為 t,t1屬性被稱為類屬性,既然是類屬性,那麼根據從C++/Java這種靜態語言使用的經驗來判斷,類屬性應該是為其例項所共享的。

其中 int型別為不可變物件,可以看到例項方法a每次呼叫其id一直是一樣,但是看最後獲取例項a和例項b的int物件時:看到雖然ID相同,但是值不同,而例項方法每次呼叫的ID相同

也就是說 類屬性如果是不可變物件:類屬性是被例項所共享的,不可變物件每次修改都是重新賦值, 在對類例項屬性為不可變物件進行賦值的時候,實際上根據規則,每次引用或者修改的都是上一級的固定地址

而list型別為可變物件,例項方法a每次呼叫都改變ID,列表內容增加,而在呼叫例項b 看到list的ID 又變成了第一次呼叫實力方法a時的ID

根據Python中屬性的獲取存在一個向上查詢機制, 每一個例項的每次呼叫,都要記錄他的上一級關係,如果根據可變物件記憶體地址不變是沒辦法做到的,所以每次呼叫要創開闢的記憶體地址

接著看情景二:
Python屬於動態強型別的語言,在很多地方和靜態語言不同, Python中屬性的獲取存在一個向上查詢機制
Python中一切皆物件,Test屬於類物件,obj1屬於例項物件,從物件的角度來看,Test與obj1是兩個無關的物件,但是,Python通過下面的查詢樹建立了類物件Test與例項物件obj1、obj2之間的關係。
如圖所示

    Test
     |
  -----
  |     |  
   obj1   obj2

當呼叫Test.test時,直接從Test獲取其屬性test。
但是情形1中呼叫obj1.test時,Python按照從obj1到Test的順序由下到上查詢屬性test。
值得注意的這時候obj1是沒有屬性test的,於是,Python到類Test中去查詢,成功找到,並顯示出來。所以,從現象上來看,Test的屬性test確實是共享給其所有例項的,雖然這裡只是從查詢樹的形式模擬了其關係。
Python中的屬性設定

原帖子的作者也指出問題的關鍵在於情形2中obj1.test += 2。
為什麼呢?
上面我們指出test += 2包含了屬性獲取及屬性設定兩個操作。即obj1.test+= 2等價於obj1.test = obj1.test + 2。
其中等式右側的obj.test屬於屬性獲取,其規則是按照上面提到的查詢規則進行,即,這時候,獲取到的是Test的屬性test,所以等式左側的值為12。
第二個操作是屬性設定,即obj.test = 12。當發生屬性設定的時候,obj1這個例項物件沒有屬性test,因此會為自身動態新增一個屬性test。
由於從物件的角度,類物件和例項物件屬於兩個獨立的物件,所以,這個test屬性只屬於obj1,也就是說,這時候類物件Test和例項物件test各自有一個屬性test。
那麼,在情形3中,再次呼叫obj1.test時,按照屬性呼叫查詢規則,這個時候獲取到的是例項物件obj1的屬性test,而不是類物件Test的屬性test。