屬性就是屬於一個對象的數據或者函數,我們可以通過句點(.)來訪問屬性,同時 Python 還支持在運作中添加和修改屬性。
我們先來看看類里面的普通字段:
class Test(object): name = 'python' a = Test() print Test.name # 通過類進行訪問 print a.name # 通過實例進行訪問
我們發現都是可以訪問的。
但是,如果我們試圖修改這個屬性的話:
class Test(object): name = 'python' a = Test() Test.name = 'python good' # 通過類進行修改 print Test.name print a.name
我們發現兩者都修改成功了。
如果通過實例來修改屬性的話:
class Test(object): name = 'python' a = Test() a.name = 'python good' # 通過實例進行修改 print Test.name print a.name
我們發現類的屬性沒有修改,而實例的屬性則修改成功了。這究竟是為什么?
其實這里的情況非常類似於局部作用域和全局作用域。
我在函數內訪問變量時,會先在函數內部查詢有沒有這個變量,如果沒有,就到外層中找。這里的情況是我在實例中訪問一個屬性,但是我實例中沒有,我就試圖去創建我的類中尋找有沒有這個屬性。找到了,就有,沒找到,就拋出異常。而當我試圖用實例去修改一個在類中不可變的屬性的時候,我實際上並沒有修改,而是在我的實例中創建了這個屬性。而當我再次訪問這個屬性的時候,我實例中有,就不用去類中尋找了。
如果用一張圖來表示的話:
函數 dir() 就能查看對象的屬性:
class Test(object): name = 'python' a = Test() a.abc = 123 print dir(Test) print dir(a)
它返回一個列表,包含所有能找到的屬性的名字,這里我們為實例 a 創建了 abc 屬性,這里就能看到了。
有些同學會有疑問,為什么我才寫了幾個屬性,結果卻多出一堆我不認識的?
因為我們這里用的是新式類,新式類繼承於父類 object ,而這些我們沒有寫的屬性,都是在 object 中定義的。
python3中不管有沒有顯示的去繼承object,默認都會繼承的
Python2中如果顯示的繼承object就是新式類,沒有繼承object就是經典類
如果我們用經典類的話:
為了方便演示,下面都使用經典類,若沒有特殊說明,新舊兩式基本是一樣的。
其中 __doc__ 是說明文檔,在類中的第一個沒有賦值的字符串就是說明文檔,一般在class的第二行寫,沒有就為空字符串。
__module__ 表示這個類來自哪個模塊,我們在主文件中寫的類,其值應該為 ‘__main__’,在其他模塊中的類,其值就為模塊名。
class Test: """文檔字符串""" name = 'scolia' print Test.__doc__ print Test.__module__
文檔字符串不一定非要用三引號,只是一般習慣上用三引號表示注釋。文檔注釋也可以使用字符串的形式,實例也可以訪問,實際訪問的是創建它的類的文檔字符串,但是子類並不會繼承父類的文檔字符串,關於繼承的問題以后再講。
除了這兩個特殊的屬性之外,還有幾個常用的,雖然沒有顯示出來,但也是可以用的。
__dict__
class Test: '文檔字符串' name = 'python' a = Test() a.name = 'good' print Test.__dict__ print a.__dict__
這個屬性就是將對象內的屬性和值用字典的方式顯示出來。這里可以明顯的看出來,實例創建了一個同名的屬性。
__class__
class Test: pass a = Test() print a.__class__
print Test.__class__
得到的結果是不一樣的
a.__class結果是__main__.Test ---它表示創建這個實例的類是哪個,這里顯示是 __main__ 模塊,也就是主文件中的 Test 這個類創建的。
Test.__class__結果是type ------表示創建類對象的元類是type
但是,這並不意味不能通過實例來修改類中的屬性。我們知道對於不可修改類型的‘修改’,其實就是重新賦值。這個也和函數中的局部作用域和全局作用域類似,我在函數內部嘗試‘修改’一個不可變類型其實就是創建新的同名變量。但我卻可以訪問全局某個可修改的對象來修改它。
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 method(): print('我是實例方法') a = Test() b = Test() a.abc = method # 特意添加一個方法 a.abc() b.abc() # b 沒有這個方法
同樣的,我們也可以為類動態添加一個方法:
class Test: pass def method(self): # self 代表是實例方法,只能由實例調用 print('我是方法') Test.abc = method a = Test() a.abc()
當然一般情況下我們很少這樣做,因為這樣會變得不可控,因為你不知道某個方法在你調用的時候有沒有創建。
字段私有化:
我們可以對屬性進行私有化,以限制部分訪問,現在先說說字段私有化。
一般公有字段我們可以通過實例對象訪問,類對象訪問,類里面的方法也可以訪問。
而私有字段一般僅類里面的方法可以訪問了。
私有化的方法非常簡單,只需要在變量名前面加上兩個下划線即可:
class Test: __name = 'python' # 私有字段 def a(self): print Test.__name #內部還需要用類來方問 a = Test() a.a() print Test.__name # 在外部使用類來訪問是不行的
但是,私有化並不是語法上的強制,而是 python 隱藏了私有字段的訪問入口,所以我們可以在新的入口中訪問到私有字段:
print Test._Test__name
其格式是: 對象._類__字段名;這里是類的私有字段,所以使用的對象是類對象。
上面的代碼使用的是類對象,也可以使用實例對象進行訪問 a._Test__name 。
私有化其實就是‘混淆’了相應的屬性,這樣做還有一個好處,可以保護 __xxx 變量不會與父類中的同名變量沖突。如果父類中有也有一個 __xxx 的變量的話,父類中的變量不會被子類中 __xxx 覆蓋。如果是非私有的字段 xxx ,就會被子類中的覆蓋掉。所以私有化也是保護關鍵變量的好選擇。
們上面講的都是類中的字段的私有化,同樣的,我們也可以為實例的字段進行私有化
class Test: def __init__(self): self.__name = 'python' # 實例的私有字段 def a(self): print self.__name # 只能有內部方法能訪問 a = Test() a.a() print a.__name # 試圖通過實例訪問是訪問不到的
同樣的,這里也是隱藏了入口,訪問格式依然是一樣的,只不過這里的對象指的是實例了
print a._Test__nam
總結:公用字段可以通過類對象,實例對象,類里面的方法進行訪問。
而私有字段則一般通過類里面的方法進行訪問。
一般不建議強制訪問私有字段。