寫在之前
如果你看過昨天的文章相信你對「類」有了一些基本的認識,為了能給之後的程式設計打個稍微牢固的基礎,我們要深入到一些細節部分中去。今天我們來看類的屬性。
類屬性
首先我們在互動模式下先建立一個簡單的類:
>>> class A():
... x = 1
...
複製程式碼
上面的 A() 類中的程式碼沒有任何方法,只有 x = 1,當然如果你樂意的話,你可以寫任何東西。先不管為什麼,我們繼續在互動模式下敲下面的程式碼:
>>> A.x
1
複製程式碼
A 是剛剛建立的類的名字,x 是類中的一個變數,它引用的物件是整數 1。通過 A.x 的方式就能得到整數 1,。像這樣的,類中的 x 被稱為類的屬性,而 1 是這個屬性的值,A.x 是呼叫類屬性的方式。
我們在這裡談到了「屬性」,請不要忽視這個詞,在很多的領域都有它的身影。
下面我們回到之前 A 類的那個例子上。如果要呼叫類的某個屬性,其方法是用英文的句號,就如我們例子中的 A.x。類的屬性僅僅與其所定義的類繫結,並且這種屬性本質上就是類裡的變數。它的值不依賴任何的例項,只是由類中所寫的變數賦值語句確定。所以類的屬性還有另外一個名字 -- 「靜態變數」。
我在前面的文章中說過很多次,在 Python 中 「萬物皆物件」,類當然也不例外,它也是物件,凡是物件都具有屬性和方法,而屬性是可以增加刪除和修改的。既然如此,那麼對於之前的類 A,都可以對其目前所擁有的屬性進行修改,也可以增加新的屬性。
>>> A.y = 2
>>> A.y
2
複製程式碼
上述程式碼給類 A 增加了一個新的屬性 y,並賦值為 2。
>>> del A.x
>>> A.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class A has no attribute 'x'
複製程式碼
上述程式碼刪除了一個已有的屬性 x,A.x 屬性被刪除後,如果再呼叫,就會出現異常。A.y 依然存在,我們可以修改 y 這個類的屬性的值:
>>> A.y = 10000
>>> A.y
10000
複製程式碼
y 是我們在 A 類中自己定義的屬性,其實在一個類建立的同時,Python 也讓這些類具有了一些預設的屬性,可以用我們熟悉的 dir() 來檢視類的所有屬性,當然也包括方法:
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'y']
複製程式碼
我們仔細觀察上面的結果,可以發現一個特殊的屬性 dict,之所以用“特殊” 這個詞來修飾,是因為它也是以雙下劃線開頭和結尾的,類似於昨天文章中我們所見的 init()。在類裡面,凡事以雙下劃線開頭和結尾命名的屬性和方法,我們都稱它們為“特殊**”。
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'y': 10000, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})
複製程式碼
下面我再說幾種類的特殊屬性的含義:
-
A.name:以字串的形式返回類的名字,需要注意的是這時候得到的僅僅是一個字串,而不是一個類物件。
-
A.doc:顯示類的文件。
-
A.base:類 A 的所有父類。如果是按照上面方式定義的類,應該顯示 object,因為以上所有的類都繼承了它。等到學習了“繼承”,再來看這個屬性,內容就豐富了。
-
A.dict:以字典形式顯示類的所有屬性。
-
A.module:類所在的模組。
這裡稍微解釋一下 A.module,我們對類 A 做如下操作:
>>> A.__module__
'__main__'
複製程式碼
說明這個類所描述的模組是 mian()。
最後讓我們來對類的屬性進行一個總結:
1.類屬性跟類繫結,可以自定義,刪除,修改值,也可以隨時增加類屬性。
2.每個類都有一些特殊屬性,通常情況下特殊屬性是不需要修改的,雖然有的特殊屬性可以修改,比如 A.doc。
對於類,除了屬性,還有方法。但是類中的方法,因為牽扯到例項,所以我們還是通過研究例項來理解類中的方法。
我在之前的文章中說過,類是物件的定義,例項才是真實的東西。比如「人」 是一個類,但是「人”」終究不是具體的某個會喘氣的,只有「rocky」 才是具體的東西,但他是具有「人」這個類所定義的屬性和方法。「rocky」 就是「人」 這個類的例項。
建立例項
建立例項並不是很難的事情,只需要呼叫類就可以實現:
>>> class man():
... sex = '男'
...
>>> rocky = man()
>>> rocky
<__main__.man instance at 0x00000000004F3688>
複製程式碼
如果不是用很嚴格的說法的話,上面的這個例子就是建立了一個例項 rocky。這裡有一點需要我們注意的是,呼叫類的方法和呼叫類的函式類似,如果僅僅是寫 man() 的話,則是建立了一個例項:
>>> man()
<__main__.man instance at 0x0000000002577D88>
複製程式碼
而 rocky = man() 本質上是將變數 rocky 與例項物件 man() 建立引用關係,這種關係就如同我們在剛開始的時候學的賦值語句 x = 1 是同樣的效果。
那麼對於一個例項來說這個建立的過程是怎麼進行的呢?我們繼續來看:
class Person:
"""
具有通常類的結構的 Person 類
"""
def __init__(self,name):
self.name = name
def get_name(self):
return self.name
def get_sex(self,sex):
per_sex = {}
per_sex[self.name] = sex
return per_sex
複製程式碼
例項我們用 boy = Person('rocky') ,當然了,在這裡你可以建立很多個例項,還記得那句話麼:類是例項的工廠。
當我們建立完例項,接下來就是呼叫類,當類被呼叫以後,先是建立一個例項物件,然後檢查是否有 init(),如果有的話就呼叫這個方法,並且將例項物件作為第一個引數 self 傳進去,如果沒有的話,就只是返回例項物件。
我之前也說過,init() 作為一個方法是比較特殊的,在它裡面,一般是規定一些屬性或者做一些初始化,讓類具有一些基本的屬性,但是它沒有 return 語句,這是 init() 區別於一般方法的地方:
>>> class fun:
... def __init__(self):
... print('this is init()')
... return 1
...
>>> f = fun()
this is init()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() should return None
複製程式碼
上面的執行結果出現了異常,並且明確說明了「init() should return None」,所以不能有 return,如果非要帶上的話,只能是 return None,索性就不要寫了。
由此可知對於 init() ,除了第一個引數必須是 self,還要求不能有 return 語句,其他方面和普通函式就沒有什麼區別了。比如引數和裡面的屬性,你就可以像下面這樣來做:
>>> class Person:
... def __init__(self,name,sex = '男',age = 10):
... self.name = name
... self.sex = sex
... self.age = age
...
複製程式碼
例項我們建立好了以後,我們接下來就要研究例項的內容,首先來看的是例項屬性。
例項屬性
和類屬性相似,例項所具有的屬性叫做 “例項屬性”:
>>> class A:
... x = 1
...
>>> f = A()
複製程式碼
類已經有了一個屬性 A.x = 1,那麼由類所建立的例項也應當具有這個屬性:
>>> f.x
1
複製程式碼
除了 f.x 這個屬性以外,例項也具有其它的屬性和方法,我們依然用 dir 方法來看:
>>> dir(f)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x']
複製程式碼
例項屬性和類屬性最主要的不同是在於,例項屬性可以隨意的更改:
>>> f.x += 10
>>> f.x
11
複製程式碼
上面就是把例項屬性修改了,但是類屬性並沒有因為例項屬性的修改而發生變化,正如我們在前幾天的文章中所說的那樣,類屬性是與類捆綁的,不受例項的影響。
>>> A.x
1
複製程式碼
上述的結果正好印證了這一點 -- 類屬性不因例項屬性改變而改變。既然如此,那麼 f.x += 10 又改變了什麼呢?
其實就是例項 f 又重新建立了一個新的屬性,但是這個新的屬性和原先舊的屬性是一個名字,都是 f.x,所以相當於原先舊的屬性被 “掩蓋”了,只能訪問到新的屬性,所以值是 11。
>>> f.x
11
>>> del f.x
>>> f.x
1
複製程式碼
由上面的例子可以看出,既然新的 f.x “掩蓋”了舊的 f.x,只要把新的 f.x 刪除,舊的 f.x 就可以顯現出來。
例項的改變不會影響到類,但是類屬性可以影響到例項屬性,因為例項就是通過呼叫類來建立的:
>>> A.x += 10
>>> A.x
11
>>> f.x
11
複製程式碼
如果是同一個屬性 x,那麼例項屬性跟著類屬性的改變而改變,當然,這個是針對於像字串這種不可變物件而言的,對於類中如果引用的是可變物件的資料,則情形會有所不同,因為可變物件的資料是可以原地進行修改的:
>>> class B:
... y = [1,2,3,4]
...
>>> B.y #類屬性
[1, 2, 3, 4]
>>> f = B()
>>> f.y #例項屬性
[1, 2, 3, 4]
>>> B.y.append('5')
>>> B.y
[1, 2, 3, 4, '5']
>>> f.y
[1, 2, 3, 4, '5']
>>> f.y.append('66')
>>> B.y
[1, 2, 3, 4, '5', '66']
>>> f.y
[1, 2, 3, 4, '5', '66']
複製程式碼
通過上面的程式碼我們可以看出,當類中的變數引用的是可變物件的時候,類屬性和例項屬性都能夠直接修改這個物件,從而增加另一方的值。
還有一點我們已經知道了增加一個類屬性,相應的例項屬性也會增加,但是反過來就不成立了:
>>> B.x = 'aa'
>>> f.x
'aa'
>>> f.z = 'abcd'
>>> f.z
'abcd'
>>> B.z
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class B has no attribute 'z'
複製程式碼
可以看出類並沒有接納例項例項增加的屬性。
寫在之後
更多內容,歡迎關注公眾號「Python空間」,期待和你的交流。