零基礎學習 Python 之細說類屬性 & 例項

Python空間發表於2019-02-19

寫在之前

如果你看過昨天的文章相信你對「類」有了一些基本的認識,為了能給之後的程式設計打個稍微牢固的基礎,我們要深入到一些細節部分中去。今天我們來看類的屬性。

類屬性

首先我們在互動模式下先建立一個簡單的類:

>>> 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空間」,期待和你的交流。

在這裡插入圖片描述

相關文章