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

寫在以前

若是你看過昨天的文章相信你對「類」有了一些基本的認識,爲了能給以後的編程打個稍微牢固的基礎,咱們要深刻到一些細節部分中去。今天咱們來看類的屬性。編程

類屬性

首先咱們在交互模式下先建立一個簡單的類:bash

>>> class A():
...    x = 1
...
複製代碼

上面的 A() 類中的代碼沒有任何方法,只有 x = 1,固然若是你樂意的話,你能夠寫任何東西。先無論爲何,咱們繼續在交互模式下敲下面的代碼:app

>>> A.x
1
複製代碼

A 是剛剛創建的類的名字,x 是類中的一個變量,它引用的對象是整數 1。經過 A.x 的方式就能獲得整數 1,。像這樣的,類中的 x 被稱爲類的屬性,而 1 是這個屬性的值,A.x 是調用類屬性的方式。ssh

咱們在這裏談到了「屬性」,請不要忽視這個詞,在不少的領域都有它的身影。函數

下面咱們回到以前 A 類的那個例子上。若是要調用類的某個屬性,其方法是用英文的句號,就如咱們例子中的 A.x。類的屬性僅僅與其所定義的類綁定,而且這種屬性本質上就是類裏的變量。它的值不依賴任何的實例,只是由類中所寫的變量賦值語句肯定。因此類的屬性還有另一個名字 -- 「靜態變量」。學習

我在前面的文章中說過不少次,在 Python 中 「萬物皆對象」,類固然也不例外,它也是對象,凡是對象都具備屬性和方法,而屬性是能夠增長刪除和修改的。既然如此,那麼對於以前的類 A,均可以對其目前所擁有的屬性進行修改,也能夠增長新的屬性。ui

>>> A.y = 2
>>> A.y
2
複製代碼

上述代碼給類 A 增長了一個新的屬性 y,並賦值爲 2。this

>>> 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 這個類的屬性的值:spa

>>> A.y = 10000
>>> A.y
10000
複製代碼

y 是咱們在 A 類中本身定義的屬性,其實在一個類創建的同時,Python 也讓這些類具備了一些默認的屬性,能夠用咱們熟悉的 dir() 來查看類的全部屬性,固然也包括方法:3d

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

在這裏插入圖片描述
相關文章
相關標籤/搜索