1. 程式人生 > >python中的類屬性和例項屬性

python中的類屬性和例項屬性

原文地址:http://www.cnblogs.com/scolia/p/5582268.html

   屬性就是屬於一個物件的資料或者函式,我們可以通過句點(.)來訪問屬性,同時 python 還支援在運作中新增和修改屬性。

  而資料變數,類似於: name = 'scolia' 這樣的形式,會稱其為欄位;而類裡面的函式,又稱為方法。而方法又分為例項方法,類方法和靜態方法,這些我們以後在講。

  我們先來看看類裡面的普通欄位:

複製程式碼
class Test(object):
    name = 'scolia'

a = Test()
print Test.name  # 通過類進行訪問
print
a.name # 通過例項進行訪問
複製程式碼

  我們發現都是可以訪問的。

  但是,如果我們試圖修改這個屬性的話:

複製程式碼
class Test(object):
    name = 'scolia'

a = Test()
Test.name = 'scolia good'   # 通過類進行修改
print Test.name
print a.name
複製程式碼

  我們發現兩者都修改成功了。

  如果通過例項來修改屬性的話:

複製程式碼
class Test(object):
    name = 'scolia'

a = Test()
a.name = 'scolia good
' # 通過例項進行修改 print Test.name print a.name
複製程式碼

  我們發現類的屬性沒有修改,而例項的屬性則修改成功了。這究竟是為什麼?

  其實這裡的情況非常類似於區域性作用域和全域性作用域。

  我在函式內訪問變數時,會先在函式內部查詢有沒有這個變數,如果沒有,就到外層中找。這裡的情況是我在例項中訪問一個屬性,但是我例項中沒有,我就試圖去建立我的類中尋找有沒有這個屬性。找到了,就有,沒找到,就丟擲異常。而當我試圖用例項去修改一個在類中不可變的屬性的時候,我實際上並沒有修改,而是在我的例項中建立了這個屬性。而當我再次訪問這個屬性的時候,我例項中有,就不用去類中尋找了。

  如果用一張圖來表示的話:

  函式  dir()  就能檢視物件的屬性:

複製程式碼
class Test(object):
    name = 'scolia'

a = Test()
a.abc = 123
print dir(Test)
print dir(a)
複製程式碼

  它返回一個列表,包含所有能找到的屬性的名字,這裡我們為例項 a 建立了 abc 屬性,這裡就能看到了。

  有些同學會有疑問,為什麼我才寫了幾個屬性,結果卻多出一堆我不認識的?

  因為我們這裡用的是新式類,新式類繼承於父類 object ,而這些我們沒有寫的屬性,都是在 object 中定義的。

  如果我們用經典類的話:

  為了方便演示,下面都使用經典類,若沒有特殊說明,新舊兩式基本是一樣的。

  其中 __doc__ 是說明文件,在類中的第一個沒有賦值的字串就是說明文件,一般在class的第二行寫,沒有就為 None。

   __module__ 表示這個類來自哪個模組,我們在主檔案中寫的類,其值應該為 ‘__main__’,在其他模組中的類,其值就為模組名。

複製程式碼
class Test:
    """文件字串"""
    name = 'scolia'

print Test.__doc__
print Test.__module__
複製程式碼

  文件字串不一定非要用三引號,只是一般習慣上用三引號表示註釋。例項也可以訪問,實際訪問的是建立它的類的文件字串,但是子類並不會繼承父類的文件字串,關於繼承的問題以後再講。

  除了這兩個特殊的屬性之外,還有幾個常用的,雖然沒有顯示出來,但也是可以用的。

 __dict__ 

複製程式碼
class Test:
    """文件字串"""
    name = 'scolia'

a = Test()
a.name = 'good'
print Test.__dict__
print a.__dict__
複製程式碼

  這個屬性就是將物件內的屬性和值用字典的方式顯示出來。這裡可以明顯的看出來,例項建立了一個同名的屬性。

  我們也可以使用  vars()  函式,傳給函式一個物件,其結果也是一樣的。

 __class__ 

class Test:
    pass

a = Test()
print a.__class__

  這個屬性只有例項中有,它表示建立這個例項的類是哪個,這裡顯示是 __main__ 模組,也就是主檔案中的 Test 這個類建立的。

  但是其返回值並不是字串,而是建立例項的類物件,但新舊式類返回有點不同:

經典類:

新式類:

  因為返回的是建立例項的類物件,也就是說我們也要用這個返回的類物件再進行例項化。

b = a.__class__()
print b

  這裡用的是新式類為例,實際上新舊式類都能這樣做。下面是舊式類的:

  但是,這並不意味不能通過例項來修改類中的屬性。我們知道對於不可修改型別的‘修改’,其實就是重新賦值。這個也和函式中的區域性作用域和全域性作用域類似,我在函式內部嘗試‘修改’一個不可變型別其實就是建立新的同名變數。但我卻可以訪問全域性某個可修改的物件來修改它。

  看程式碼:

複製程式碼
class Test:
    list1 = []

a = Test()
a.list1.append(123)     # 同通過例項修改類中的列表
print Test.list1
print a.list1
複製程式碼

  我通過例項訪問到了一個物件,這個物件是可修改的,所以我可以修改這個物件。這相當於直接操作那個物件。

  但是,等號這樣的顯式賦值行為還是建立新的物件:

a.list1 = [123]   # 顯式的建立一個新物件

  這可能有點繞,需要對變數的賦值、可修改物件與不可修改物件的瞭解,可以參考我以前的相關博文。

  這裡又有個問題了,我們通常使用 __init__ 來初始化時,會為其新增資料屬性,例如 self.name = xxx ,但是卻幾乎不會為例項新增方法,也就是說例項中的方法,都是在類中找的。這樣做其實有好處,因為方法一般是通用的,如果每一個例項都要儲存一套方法的話,實在太浪費資源,而把這些方法統一儲存到類中,用到的時候來類裡找,就節約了許多。

  當然,我們也可以任性地為某個例項新增方法,python 支援動態新增屬性。

複製程式碼
class Test:
    pass

def fangfa():
    print '我是某個例項的方法'

a = Test()
b = Test()
a.abc = fangfa  # 特意新增一個方法
a.abc()
b.abc()     # b 沒有這個方法
複製程式碼

  同樣的,我們也可以為類動態新增一個方法:

複製程式碼
class Test:
    pass

def fangfa(self):   # self 代表是例項方法,只能由例項呼叫
    print '我是方法'

Test.abc = fangfa
a = Test()
a.abc()
複製程式碼

  關於方法以後再細說。

  當然一般情況下我們很少這樣做,因為這樣會變得不可控,因為你不知道某個方法在你呼叫的時候有沒有建立。

欄位私有化:

  我們可以對屬性進行私有化,以限制部分訪問,但關於方法私有化以後在講,現在先說說欄位私有化。

  一般公有欄位我們可以通過例項物件訪問,類物件訪問,類裡面的方法也可以訪問。

  而私有欄位一般僅類裡面的方法可以訪問了。

  私有化的方法非常簡單,只需要在變數名前面加上兩個下劃線即可:

複製程式碼
class Test:
    __name = 'scolia'   # 私有欄位

    def a(self):
        print Test.__name   #內部還需要用類來方問

a = Test()
a.a()
print Test.__name   # 在外部使用類來訪問
複製程式碼

  我們可以看到在外部通過類來訪問是不行的,但內部通過類來訪問卻可以。

  同樣的,通過例項訪問也不允許。

print a.__name

  但是,私有化並不是語法上的強制,而是 python 隱藏了私有欄位的訪問入口,所以我們可以在新的入口中訪問到私有欄位:

print Test._Test__name

  其格式是: 物件._類__欄位名;這裡是類的私有欄位,所以使用的物件是類物件。

  上面的程式碼使用的是類物件,也可以使用例項物件進行訪問 a._Test__name 。

  私有化其實就是‘混淆’了相應的屬性,這樣做還有一個好處,可以保護 __xxx 變數不會與父類中的同名變數衝突。如果父類中有也有一個 __xxx 的變數的話,父類中的變數不會被子類中 __xxx 覆蓋。如果是非私有的欄位 xxx ,就會被子類中的覆蓋掉。所以私有化也是保護關鍵變數的好選擇。

  我們上面講的都是類中的欄位的私有化,同樣的,我們也可以為例項的欄位進行私有化。

複製程式碼
class Test:
    def __init__(self):
        self.__name = 'scolia'   # 例項的私有欄位

    def a(self):
        print self.__name   # 只能有內部方法能訪問

a = Test()
a.a()
print a.__name  # 試圖通過例項訪問
複製程式碼

  當然,因為例項屬性類中並沒有,所以不用考慮通過類來訪問了。

  同樣的,這裡也是隱藏了入口,訪問格式依然是一樣的,只不過這裡的物件指的是例項了。

print a._Test__name 

  總結:公用欄位可以通過類物件,例項物件,類裡面的方法進行訪問。

     而私有欄位則一般通過類裡面的方法進行訪問。

       一般不建議強制訪問私有欄位。