零基礎學習 Python 之繼承

寫在以前

面向對象的程序設計都三個主要的特徵:封裝,繼承,多態,這個也是類裏面的重要內容,這三個特徵我會從今天開始依次開始寫,今天咱們先來看第一個:「封裝」,這一部分我會分兩次來寫,接下來進入正題。bash

概念

對於「繼承」的概念,咱們先來看在《維基百科》中的定義:函數

繼承(Inheritance)是面向對象軟件技術當中的一個概念。若是一個類別 A 「繼承」自另外一個類別 B,就把這個 A 稱爲 「B 的子類別」,而把 B 稱爲 「A 的父類別」,也能夠稱爲 「B 是 A 的超類」。學習

「繼承」可使得子類具備父類的各類屬性和方法,而不須要再去重複編寫相同的代碼。在令子類繼承父類的同時,能夠從新去定義某些屬性,並能夠重寫某些方法,簡單點來講就是覆蓋掉父類的屬性和方法,使其擁有和父類不一樣的功能。此外,還能夠爲子類追加新的屬性和方法,這都是些常見的作法。ui

因此由上面的描述咱們能夠知道使用繼承的好處:一是能夠實現代碼的重用,但又不只僅是代碼重用;再者就是能夠實現屬性和方法的繼承。固然了這個並非所有,隨着咱們以後的學習,相信你對繼承會有更深層次的瞭解。spa

若是你以前學過 Java 或者其它 OOP 語言的話,可能會對繼承有不同的理解。其實在 Python 裏,由於存在**「鴨子類型」(duck typing)** ,使得接口定義的重要性大大的下降,從而繼承的做用也被進一步的削弱了。至於什麼是鴨子類型,由於如今還有幾個沒有講,因此這個我會在以後的文章單獨拿出來講。debug

再補充一點,若是你看過我前面的文章,你可能看到過我過全部的類都是 object 的子類,可是這裏並無顯式的把 object 寫出來,而是用隱式的方法繼承了 object。總而言之,object 就是全部類的父類。設計

單繼承

所謂的單繼承,就是隻從一個父類那裏繼承。code

>>> class A:
...    pass
... 
>>> class B(A):
...    pass
...
複製代碼

上面的例子,類 A 是一個普通的類,類 B 則是定義的一個子類,它用 B(A) 的形式繼承了類 A(父類),雖然這個父類裏什麼也沒有。cdn

子類 B 繼承父類 A 的方式就是在類名後面的括號裏寫上父類的類名。既然是繼承了父類,那麼根據咱們上面說的,父類的一切都帶到了子類裏。上面也說了,在 Python3 裏全部的類都是 object 的子類,可是不用寫出 object,可是對於繼承其它的類就不能隱藏了,必需要顯式的寫上父類的類名。對象

還記得咱們前面的文章中寫過的能夠獲得類的父類的那個特殊函數嗎?咱們在這裏能夠用一下:

>>> A.__base__
<class 'object'>
>>> B.__base__
<class '__main__.A'>
複製代碼

爲了深刻的瞭解一下「繼承」的做用,咱們下面讓父類作點兒事情:

>>> class A:
...    def __init__(self):
...            print('my name is rocky')
... 
>>> class B(A):
...    pass
... 
>>> b = B()
my name is rocky
複製代碼

父類 A 中增長了初始化函數,而後子類 B 繼承了它。咱們已經知道,當創建實例的時候,首先要執行類中的初始化函數,由於子類 B 繼承了父類,就把父類中的初始化函數拿到了子類裏面,因此在 b = B() 的時候,執行了父類中定義的初始化函數,這就是繼承,並且是從一個父類那裏繼承來的,因此稱爲單繼承。

下面咱們來看一個相對完整一些的例子:

class Person:
   def __init__(self,name):
       self.name = name

   def height(self,m):
       h = dict((['height',m],))
       return h

   def sex(self,se):
       sex1 = se
       return sex1

class Boy(Person):
   def get_name(self):
       return self.name

if __name__ == "__main__":
   lee = Boy('rocky')
   print(lee.get_name())
   print(lee.height(170))
   print(lee.sex('男'))
複製代碼

運行上面的代碼,所得的結果以下:

rocky
{'height': 170}
男
複製代碼

首先咱們定義了一個 Person 類,而後定義了一個子類 Boy。

在子類 Boy 中只寫了一個方法 get_name(),可是由於繼承了 Person,那麼 Boy 就擁有了 Person 中的所有方法和屬性,在子類 Boy 的方法 get_name() 中,使用了屬性 self.name,可是在類 Boy 中並無建立這個屬性,只是由於其繼承了 Person,在父類中有初始化函數。因此在使用子類建立實例的時候,必需要傳一個參數,而後再調用方法。對於實例方法 lee.height(170) 也是由於繼承的緣故。

在上面的程序中,子類 Boy 並無和父類有重複的屬性和方法,但有時候會出現下面的這種狀況:

class Boy(Person):
   def __init__(self):
       self.name = 'snow'

   def get_name(self):
       return self.name
複製代碼

在子類裏面也有一個初始化函數,而且定義了一個實例屬性 self.name = 'snow',而在父類中也有初始化函數,在這種狀況下運行之後會出現什麼結果呢?你先猜一下,猜完了繼續往下看:

TypeError: __init__() takes 1 positional argument but 2 were given
複製代碼

嚯,居然報錯了。報錯的消息是建立實例的時候,傳入的參數個數多了。這個的根源在於,子類 Boy 中的初始化函數只有一個 self,但由於跟父類的初始化函數重名,雖然繼承了父類,可是將父類中的初始化函數覆蓋掉了,因此如今的實例化子類不該該再顯式的傳參數。

if __name__ == "__main__":
   lee = Boy()
   print(lee.get_name())
   print(lee.height(170))
   print(lee.sex('男'))
複製代碼

如此修改之後再運行,顯示的結果以下:

snow
{'height': 170}
男
複製代碼

從結果中咱們不難看出來,若是子類中的屬性或者方法與父類同名,那麼就再也不繼承父類的該屬性或方法。

調用被覆蓋的方法

咱們昨天說過,若是子類裏有和父類一樣名稱的方法和屬性,那麼父類相應的部分再也不被繼承到子類。那麼若是有這麼一種狀況,若是子類還想繼續使用父類的該方法,那麼該怎麼辦?咱們能夠對子類作以下修改,例子仍是昨天的那個例子:

class Boy(Person):
  def __init__(self,name):
      Person.__init__(self,name)
      self.real_name = 'snow'

  def get_name(self):
      return self.name
複製代碼

請仔細觀察 Boy 的初始化方法,與昨天的例子有所不一樣,爲了能繼續在子類中使用父類的初始化方法,以類方法的方式調用 Person.init(self,name),此外在子類的初始化方法的參數中,要增長相應的參數 name。

接下來讓咱們實例化子類:

if __name__ == "__main__":
  lee = Boy('rocky')
  print(lee.real_name)
  print(lee.get_name())
複製代碼

此刻的運行結果估計你也能猜出來:

snow
rocky
複製代碼

這樣使父類中被覆蓋的方法能再次在子類中使用。可是上述的方式有一個問題,那就是若是父類的名稱由於某種玄學的方式被改變了,子類所對應的父類的類名也要改,若是你忘記了改,就會出現異常,程序少還能夠,程序若是特別多的話,你可能會忘記一兩個地方,而後就苦逼的 debug 了。

你想咱們號稱簡潔優雅的 Python 怎麼可能會容忍這麼費事的東西存在呢,因而 Python 創了一個很是巧妙的方法 -- super

class Boy(Person):
  def __init__(self,name):
      #Person.__init__(self,name)
      super(Boy,self).__init__(name)
      self.real_name = 'snow'

  def get_name(self):
      return self.name
複製代碼

在上面的代碼中咱們只修改了一處,運行程序後顯示的結果和之前同樣,固然關於 super 還有一些別的用法,在這不作深究,感興趣的能夠自行 Google。

多重繼承

昨天的文章中咱們學了單繼承,所謂單繼承就是隻從一個父類那繼承,推展開來,多重繼承就是繼承能夠來自多個父類,即一個子類的父類不止一個。請看下面的例子:

class Person:
   def hands(self):
       print('two hands')
   def hair(self,color):
       print('The hair color is: ',color)

class Boy:
   age = 23
   def name(self):
       print('The boy name is rocky')

class Children(Person,Boy):
   pass

if __name__ == "__main__":
   lee = Children()
   lee.hands()
   lee.hair('black')
   lee.name()
   print(lee.age)
複製代碼

在上面的程序中,前面有兩個類分別是 Person 和 Boy,而後第三個類是 Children,它繼承了前面的兩個類,繼承的方法和單繼承差很少,也是在類名後面的括號裏把要繼承的兩個類的類名寫上。接着我實例化了類 Children,既然它繼承了上面的兩個類,那麼上面兩個父類的方法就能夠拿來用。

運行一下程序,結果以下所示:

two hands
The hair color is: black
The boy name is rocky
23
複製代碼

由上面的幾個例子咱們已經能很清楚的知道繼承的特色,接下來咱們還有必要了解一下多重繼承的順序問題。在這裏有一個小問題:若是一個子類繼承了兩個父類,而且兩個父類中都有相同的屬性或者方法,那麼實例化以後的子類所調用的該屬性或方法是屬於哪一個父類的呢?下面就讓咱們來試一下:

class P1:
   def name(self):
       print('name is P1')

class P2:
   def name(self):
       print('name is P2')
   def age(self):
       print('P2 age is 23')

class Q1(P1,P2):
   pass

class Q2(P1,P2):
   def age(self):
       print('Q2 age is 110')

class A(Q1,Q2):
   pass

if __name__ == "__main__":
   print(A.__mro__)
   cla = A()
   cla.age()
   cla.name()
複製代碼

上述程序的運行結果以下:

(<class '__main__.A'>, <class '__main__.Q1'>, <class '__main__.Q2'>, <class '__main__.P1'>, <class '__main__.P2'>, <class 'object'>)
Q2 age is 110
name is P1
複製代碼

其中 print(A.mro) 能夠打印出類的繼承順序。若是要執行 age() 方法,首先要在 Q1 類中查看有沒有這種方法,若是沒有,再看 Q2 類,若是仍是沒有的話再看 Q1 類所繼承的 P1 類,找到後就執行該方法,一樣 name() 也是按照這樣的順序,在 Q2 類中就找到了該方法。

上述的這種對繼承屬性和方法的搜索叫作「廣度優先搜索」,至於爲何是這樣,感興趣的能夠去 Google 一下。

寫在以後

更多內容,歡迎關注公衆號「Python空間」,期待和你的交流。

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