【Python入門】12.物件導向程式設計之 三大特徵:封裝、繼承和多型 & 鴨子型別是什麼?...

weixin_34075551發表於2018-08-12

*寫在前面:為了更好的學習python,博主記錄下自己的學習路程。本學習筆記基於廖雪峰的Python教程,如有侵權,請告知刪除。歡迎與博主一起學習Pythonヽ( ̄▽ ̄)ノ *


目錄

物件導向程式設計
封裝
• 封裝資料
• 呼叫封裝資料
繼承
• 多繼承
• Mixin
多型
鴨子型別

物件導向程式設計

物件導向程式設計有三大特徵,分別是封裝、繼承和多型。

封裝

封裝,顧名思義是將資料封裝到某處,在要用的時候再從某處呼叫。

封裝資料

首先定義一個class(類)

class Animal(object):

    def __init__(self,name,age):
        self.name = name
        self.age = age

然後定義建立一個對應的Instance(例項)

dog = Animal('dog',3)

事實上,在建立該例項的同時,就把資料'dog'和3分別封裝到dog的name和age屬性中,這個過程就是資料封裝。

呼叫封裝資料

呼叫封裝資料有兩種方法,一種是通過物件直接呼叫:

>>>dog.name
dog
>>>dog.age
3

另一種是通過在類的內部定義方法來間接呼叫::

class Animal(object):
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def detail(self):                           #再建立一個方法來呼叫資料
        print('name = %s' % self.name)
        print('age = %d' % self.age)
>>>dog.detail()
name = dog 
age = 3 

這樣的話,從外部看Animal類,只需要知道建立例項時給出name和age,至於如何列印,都在Animal內部定義,這樣就把這些資料和邏輯封裝起來,可以簡單呼叫卻不用知道內部的細節。

繼承

OOP程式設計中,在定義一個class時,可以從另一個class繼承下來,新的class稱為子類或派生類被繼承的class稱為父類或超類、基類。舉些簡單的例子:

父類 子類
動物 狗,貓,熊
文具 筆,尺子,橡皮

這跟類與例項的關係有點像,只不過在這裡兩者都是類。

繼承最大的好處就是子類可以沿用父類裡面的全部功能,比如我們定義一個繼承Animal型別的Dog類。

class Dog(Animal):
    def __init__(self,name,age):
        self.name = name
        self.age = age

這時候我們再建立一個Dog對應的例項dog,此時dog就可以用Animal的方法了。

>>>dog = Dog('pupy',1)
>>>dog.detail()
name = pupy 
age = 2 

可見在Dog中並沒有定義detail這一方法,而例項dog是繼承了Animal裡面的detail方法。

多繼承

在java和C#中只能繼承一類,而在Python中允許繼承多類,舉個簡單的例子。

class D(object):                      #定義一個父類D
    def say(self):
        print('D.say')

class B(D):                           #B類繼承D類
    def say(self):
        print('B.say')
        
class C(D):                           #C類繼承D類
    def say(self):
        print('C.say')
        
class A(B,C):                         #A類繼承B類和C類

這個時候A就繼承了兩個類,B與C。現在有個問題是當A的例項在.say()的時候,是執行哪個類的say方法呢?看一下輸出結果:

>>>a = A()
>>>a.say()
B.say 

在多繼承中,在呼叫方法時,會按照廣度優先的方式去查詢對應的方法。

在本例中,先查詢A類有沒有say(),然後查詢B類,再查詢C類,最後查詢D類,如果都沒找到則報錯,如果找到了則不會再查詢後面的類。

(這裡插一個小知識點,在Python2及以前的版本中,存在經典類和新式類兩種類,經典類在定義時沒有繼承object,而新式類則有繼承。現在使用的便是新式類,採用廣度優先的方式查詢,而經典類採用深度優先的方式查詢,放在上例中就是A-B-D-C的順序。而在Python3即之後的版本中,已經沒有經典類和新式類的說法了,都預設採用新式類,即使在定義時沒有繼承object)

Mixin

多繼承的好處是能簡化多層次的繼承關係。這裡舉廖雪峰官方網站裡面的一個例子來說明。

假設我們要實現以下4種動物:

Dog - 狗狗;
Bat - 蝙蝠;
Parrot - 鸚鵡;
Ostrich - 鴕鳥。

按照哺乳動物Mammal和鳥類Bird歸類,我們可以設計出這樣的類的層次:

                     ┌───────────────┐
                     │    Animal     │
                     └───────────────┘
                             │
                ┌────────────┴────────────┐
                │                         │
                ▼                         ▼
         ┌─────────────┐           ┌─────────────┐
         │   Mammal    │           │    Bird     │
         └─────────────┘           └─────────────┘
                │                         │
          ┌─────┴──────┐            ┌─────┴──────┐
          │            │            │            │
          ▼            ▼            ▼            ▼
     ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
     │   Dog   │  │   Bat   │  │ Parrot  │  │ Ostrich │
     └─────────┘  └─────────┘  └─────────┘  └─────────┘

這時如果我們要新增“能跑”“能飛”的分類,層次就會變成這樣:

                              ┌───────────────┐
                              │    Animal     │
                              └───────────────┘
                                      │
                         ┌────────────┴────────────┐
                         │                         │
                         ▼                         ▼
                  ┌─────────────┐           ┌─────────────┐
                  │   Mammal    │           │    Bird     │
                  └─────────────┘           └─────────────┘
                         │                         │
                   ┌─────┴──────┐            ┌─────┴──────┐
                   │            │            │            │
                   ▼            ▼            ▼            ▼
              ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
              │  MRun   │  │  MFly   │  │  BRun   │  │  BFly   │
              └─────────┘  └─────────┘  └─────────┘  └─────────┘
                   │            │            │            │
                   │            │            │            │
                   ▼            ▼            ▼            ▼
              ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
              │   Dog   │  │   Bat   │  │ Ostrich │  │ Parrot  │
              └─────────┘  └─────────┘  └─────────┘  └─────────┘    

如果還要再分“寵物類”和“非寵物類”,那麼類的數量會呈指數增長,這樣明顯不行。這時候就需要用到多重繼承了。

先定義一個主要類層,還是按哺乳類和鳥類設計:

class Animal(object):           
    pass
    
#大類:
class Mammal(Animal):
    pass

class Bird(Animal):
    pass
    

再額外定義Runnable類和Flyable類:

class Runnable(object):
    def run(self):
        print('Running...')

class Flyable(object):
    def fly(self):
        print('Flying...')

這時候再定義動物小類,如果是能跑的哺乳類動物,則同時繼承上面兩個類即可:

class Dog(Mammal, Runnable):
    pass
class Bat(Mammal, Flyable):
    pass

這種“混入”額外功能的多重繼承設計通常稱之為Mixin。MixIn的目的就是給一個類增加多個功能。為了更好體現出繼承關係,通常在把額外功能的類名改為XxxMixin,像這樣:

class RunnableMixin(object):
    def run(self):
        print('Running...')

class FlyableMixin(object):
    def fly(self):
        print('Flying...')
        
class Dog(Mammal, RunnableMixin):
    pass
class Bat(Mammal, FlyableMixin):
    pass

多型

多型,是指一個物件具有多種形態,或者說一個型別具有多種型別的能力。

在理解多型之前,我們要對資料型別再做一點說明。實際上,在我們定義一個類的時候,也就是定義了一種資料型別。

x = int(5)                         #x是int型別
y = list(1)                        #y是list型別
a = Animal()                       #a是Animal型別
dog = Dog()                        #dog是Dog型別

接下來我們定義一個父類及其一些子類:

class Animal(object):
    pass

class Dog(Animal):
    pass

class Cat(Animal):
    pass

class Pig(Animal):
    pass

由於繼承的關係,Dog的例項dog不僅是Dog型別,還是Animal型別,可以通過isinstance()來判斷:

>>>dog = Dog()
>>>isinstance(dog, Dog)
True
>>>isinstance(dog, Animal)
True

所以,在繼承關係中,如果一個例項的資料型別是某個子類,那它的資料型別也可以被看做是父類。

接下來我們在類中增加一些方法,再定義一個函式。

class Animal(object):

    def __init__(self,name):                       #在建立例項時,繫結一個name屬性
        self.name = name

    def run(self,name):                            #定義一個方法,列印想要的內容
        print(self.name + ' is running!')

class Dog(Animal):
    pass

class Cat(Animal):
    pass

class Pig(Animal):
    pass

def run1(animal):                                  #定義一個接受animal型別的函式
    print('wow!')
    animal.run(animal.name)                        #執行animal中的run方法

horse = Animal('lin')                              #建立一個Animal類的例項
dog = Dog('pupy')                                  #建立一個Dog類的例項
cat = Cat('miumiu')                                #建立一個Cat類的例項

執行run1函式,傳入例項物件horse:

>>>run1(horse)
wow! 
lin is running! 

當我們傳入例項物件dog和cat時同樣可以執行。

>>>run1(dog)
wow! 
pupy is running! 
>>>run1(cat)
wow! 
miumiu is running! 

這就是所講的多型。對於函式run1而言,傳入的animal可以是Animal型別,也可以是Dog型別或是Cat型別的例項物件。

看到這裡,你或許會有疑問:animal只不過是一個函式的引數,難道不可以接受任何型別的資料?

是的,你沒有錯,這就是動態語言的特點。所以實質上,對於Python這種動態語言來說,並沒有多型這一說法,而是崇尚鴨子型別

鴨子型別

Duck typing,鴨子型別,是動態型別的一種風格。

這個概念的名字來源於由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:
“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”

接上面的例子,我們再定義一個類,這個類不繼承Animal。

class Xxxx(object):
    def __init__(self, name):
        self.name = name
        
    def run(self, name):
        print(self.name + ' is running!')

aaaa = Xxxx('aaaa')
 

把例項物件aaaa傳入run1函式,依舊可以執行。

>>>run1(aaaa)
wow! 
aaaa is running!

run1函式並沒有判斷傳入的引數是否為animal型別,只是判斷傳入的引數是否有run的方法,如果有,則可以執行。

這就是動態語言的鴨子型別,run1函式並不在乎傳入的aaaa是不是動物(鴨子),它只是run(走)起來像animal(鴨子),那麼把它就可以被當成動物(鴨子)。


以上就是本節的全部內容,感謝你的閱讀。

有任何問題與想法,歡迎評論與吐槽。

和博主一起學習Python吧( ̄▽ ̄)~*

相關文章