Python 學習筆記之類「物件導向,超類,抽象」

dicksonjyl560101發表於2018-07-25

一、物件魔法

在物件導向程式設計中,術語物件大致意味著一系列資料 (屬性) 以及一套訪問和操作這些資料的方法。

封裝

封裝講究結構複用,邏輯內斂,以固定介面對外提供服務。其遵循單一職責,規定每個型別僅有一個引發變化的原因。單一封裝的核心是解耦和內聚,這讓設計更簡單,清晰,程式碼更易測試和凍結,避免了不確定性。

繼承

繼承在遵循原有設計和不改變既有程式碼的前提下,新增新功能,或改進演算法。其對應開閉原則,對修改封閉,對擴充套件開放。

多型

多型特性遵循里氏替換原則,所有繼承子類應該能直接用於引用父類的場合。

我們習慣於將複雜型別的公用部分剝離出來,形成穩固的抽象類。其他引發變化的相似因素則被分離成多個子類,以確保單一職責得到遵守,並能相互替換。

我們習慣於將複雜型別的公用部分剝離出來,形成穩固的抽象類。其他引發變化的相似因素則被分離成多個子類,以確保單一職責得到遵守,並能相互替換。

**場景:**先將整體框架抽象成基類,然後每個子類僅保留單一分支邏輯。

繼承和多型

定義一個名為 Animal 的 class,有一個 run() 方法可以直接列印:

class Animal(object):
    def run(self):
        print("Animal is running...")
複製程式碼

當我們需要編寫 Dog 和 Cat 類時,就可以直接從 Animal 類繼承:

class Dog(Animal):
    pass

class Cat(Animal):
    pass
複製程式碼

對於 Dog 來說,Animal 就是它的父類,對於 Animal 來說,Dog 就是它的子類。Cat 和 Dog 類似。

繼承的好處一:子類獲得了父類的全部功能,擴充套件子類自己的功能

上例中 Animial 實現了 run() 方法,因此,Dog 和 Cat 作為它的子類也擁有 run() 方法:

dog = Dog()
dog.run()

cat = Cat()
cat.run()

'''
Animal is running...
Animal is running...
'''
複製程式碼

我們也可以擴充套件子類的方法,比如 Dog 類:

class Dog(Animal):
    def run(self):
        print("Dog is running...")
    def dog_eat(self):
        print("Eating bones...")
複製程式碼

繼承的好處二:重寫父類的功能。

無論是 Dog 還是 Cat,它們 run() 的時候,顯示的都是 Animal is running...,而對於 Dog 和 Cat 本身,應該具有自己的 run() 特性,即: Dog is running.. 和 Cat is running...,因此,對 Dog 和 Cat 類改進如下:

class Dog(Animal):
    def run(self):
        print("Dog is running...")
    def dog_eat(self):
        print("Eating bone...")

class Cat(Animal):
    def run(self):
        print("Cat is running...")
    def cat_eat(self):
        print("Eating fish...")

if __name__ == "__main__":
    dog = Dog()
    dog.run()

    cat = Cat()
    cat.run()
'''
Dog is running...
Eating bone...
Cat is running...
Eating fish...
'''
複製程式碼

當子類的 run() 覆蓋了父類的 run() 時候,執行時總是會呼叫子類的 run() 。這樣,我們就獲得了繼承的另一個好處:多型。

判斷一個變數是否是某個型別可以用 isinstance() 判斷:

>>> isinstance(dog,Animal)
>>> isinstance(cat,Animal)
'''
True
True
'''

>>> isinstance(dog,Dog)
>>> isinstance(cat,Dog)
'''
True
False
'''
複製程式碼

上面示例可以看到,dog 的資料型別既是 Animal,也是 Dog。因為 Dog 是從 Animal 繼承下來的,當我們建立 Dog 例項時,我們認為 dog 的資料型別是 Dog,但同時 dog 也是 animal 的資料型別,因為 Dog 本來就是 Animal 的一種。而 cat 的資料型別是 Animal 沒錯,但是 cat 不是 Dog 資料型別。

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

>>> b = Animal()
>>> isinstance(b, Dog)
'''
False
'''
複製程式碼

Dog 可以看成 Animal,但 Animal 不可以看成Dog。

二、超類

要指定超類,可在 class 語句中的類名後加上超類名,並將其用圓括號括起。

Filter 是一個過濾序列的通用類。實際上,它不會過濾掉任何東西。

class Filter:
    def init(self):
        self.blocked = []
    def filter(self, sequence):
        return [x for x in sequence if x not in self.blocked]

class SPAMFilter(Filter): 
    # SPAMFilter是Filter的子類 def init(self): # 重寫超類Filter的方法init
    def init(self): # 重寫超類Filter的方法init
        self.blocked = ['SPAM']
        
if __name__=="__main__":
    f = Filter()
    f.init()
    print(f.filter([1, 2, 3]))
    
'''
1, 2, 3
'''
複製程式碼

Filter 類的用途在於可用作其他類 (如將 'SPAM' 從序列中過濾掉的 SPAMFilter 類) 的基類 (超類)。

if __name__=="__main__":
	s = SPAMFilter() 
    s.init()
    a = s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
    print(a)
'''
['eggs', 'bacon']
'''
複製程式碼

請注意 SPAMFilter 類的定義中有兩個要點。

  • 以提供新定義的方式重寫了 Filter 類中方法 init 的定義。
  • 直接從 Filter 類繼承了方法 filter 的定義,因此無需重新編寫其定義。

第二點說明了繼承很有用的原因:可以建立大量不同的過濾器類,它們都從 Filter 類派生而來,並且都使用已編寫好的方法 filter。

你可以用複數形式的 __ bases __ 來獲悉類的基類,而基類可能有多個。為說明如何繼承多個類,下面來建立幾個類。

class Calculator:
    def calculate(self, expression):
        self.value = eval(expression)
class Talker:
    def talk(self):
        print('Hi, my value is',self.value)
class TalkingCalculator(Calculator, Talker): pass

if __name__=="__main__":
    tc = TalkingCalculator()
    tc.calculate('1 + 2 * 3')
    tc.talk()
   
'''
Hi, my value is 7
'''
複製程式碼

這被稱為多重繼承,是一個功能強大的工具。然而,除非萬不得已,否則應避免使用多重繼承,因為在有些情況下,它可能帶來意外的 “併發症”。

使用多重繼承時,有一點務必注意:如果多個超類以不同的方式實現了同一個方法 (即有多個同名方法),必須在class 語句中小心排列這些超類,因為位於前面的類的方法將覆蓋位於後面的類的方法。

因此,在前面的示例中,如果 Calculator 類包含方法 talk,那麼這個方法將覆蓋 Talker 類的方法 talk (導致它不可訪問)。

如果像下面這樣反轉超類的排列順序:

class TalkingCalculator(Talker, Calculator): pass 
複製程式碼

將導致 Talker 的方法 talk 是可以訪問的。多個超類的超類相同時,查詢特定方法或屬性時訪問超類的順序稱為方法解析順序 (MRO),它使用的演算法非常複雜。

三、抽象基類

一般而言,抽象類是不能例項化的類,其職責是定義子類應實 現的一組抽象方法。

下面是一個簡單的示例:

from abc import ABC, abstractmethod
class Talker(ABC): 
    @abstractmethod 
    def talk(self):
        pass
複製程式碼

這裡的要點是你使用 @abstractmethod 來將方法標記為抽象的 —— 在子類中必須實現的方法。

如果你使用的是較舊的 Python 版本,將無法在模組 abc 中找到 ABC 類。在這種情況下,需要匯入ABCMeta,並在類定義開頭包含程式碼行 __ metaclass __ = ABCMeta (緊跟在 class 語句後面並縮排)。如果你使用的是 3.4 之前的 Python 3 版本,也可使用 Talker(metaclass=ABCMeta) 代替 Talker(ABC)。

抽象類(即包含抽象方法的類)最重要的特徵是不能例項化。

>>> Talker()
	Traceback (most recent call last): 
	File "<stdin>", line 1, in <module>
 	TypeError: Can't instantiate abstract class Talker with abstract methods talk 
複製程式碼

假設像下面這樣從它派生出一個子類:

class Knigget(Talker): 
    pass 
複製程式碼

由於沒有重寫方法 talk,因此這個類也是抽象的,不能例項化。如果你試圖這樣做,將出現類似於前面的錯誤訊息。然而,你可重新編寫這個類,使其實現要求的方法。

class Knigget(Talker): 
	def talk(self):
		print("Ni!")
複製程式碼

現在例項化它沒有任何問題。這是抽象基類的主要用途,而且只有在這種情形下使用 isinstance 才是妥當的:如果先檢查給定的例項確實是 Talker 物件,就能相信這個例項在需要的情況下有方法 talk。

>>> k = Knigget()
>>> isinstance(k, Talker) True
>>> k.talk()
Ni!
複製程式碼

###四,總結

**將相關的東西放在一起。**如果一個函式操作一個全域性變數,最好將它們作為一個類的屬性和方法。

**不要讓物件之間過於親密。**方法應只關心其所屬例項的屬性,對於其他例項的狀態,讓它們自己去管理就好了。

**慎用繼承,尤其是多重繼承。**繼承有時很有用,但在有些情況下可能帶來不必要的複雜性。要正確地使用多重繼承很難,要排除其中的 bug 更難。

**保持簡單,讓方法短小緊湊。**一般而言,應確保大多數方法都能在 30 秒內讀完並理解。

推薦閱讀

 Python 學習筆記之類與例項

好書推薦

PS:以後的每一篇文章我都會推薦一本好的技術類書籍和一本成長類書籍,目前僅限於 PDF,為了激勵自己不斷學習新知識,堅持閱讀,希望讀者們能跟我一起行動起來。

Python 基礎教程(第3版)

這是我最近在閱讀的書,從整體上來看,講得很通俗易懂,是很好的 Python 入門讀物,並且已經升級到 Python 3。

豆瓣簡介

本書是經典教程的全新改版,作者根據 Python 3.0 版本的種種變化,全面改寫了書中內容,做到既能 “瞻前” 也能 “顧後”。本書層次鮮明、結構嚴謹、內容翔實,特別是在最後幾章,作者將前面講述的內容應用到了10個引人入勝的專案中,並以模板的形式介紹了專案的開發過程。本書既適合初學者夯實基礎,又能幫助 Python 程式設計師提升技能,即使是 Python 方面的技術專家,也能從書裡找到令你耳目一新的東西。

python基礎教程3.jpg

你一年的8760小時(升級版)

這本書是我在大學讀的,對我改變很大,學會了堅持早睡早起,堅持鍛鍊和學英語。艾力老師帶領了很多團幫助我們擺脫懶癌,共同進步。由於對這本書情有獨鍾,我還在簡書寫過一篇文章講述我是如果通過一本書學會早起的。

早起 - 對我影響最大的習慣

豆瓣簡介

《你一年的8760小時》自出版以來,連續幾個月位於各大排行榜前列,並有幸榮獲 “2015 年京東勵志年度新書榜 TOP1”。正是讀者的支援,讓他成了一個真正意義上的寫作者。

世界如此之大,做一個什麼樣的自己完全取決於你的選擇。艾力一直堅信,夢想和現實的距離並不遙遠。努力奮鬥,你終將成為理想中的自己。

你一年的8760小時1.jpeg

想要書籍的請在後臺留言 【180724】獲取這兩本書的 PDF 版,我相信如果讀了我簡書的文章,應該會對你有所感悟,我會盡量推薦自己讀過的好書。

Python夢工廠

相關文章