Python:多型、協議和鴨子型別

丹楓無跡發表於2019-05-05

多型

問起物件導向的三大特性,幾乎每個人都能對答如流:封裝繼承多型。今天我們就要來說一說 Python 中的多型。

所謂多型:就是指一個類例項的相同方法在不同情形有不同表現形式。多型機制使具有不同內部結構的物件可以共享相同的外部介面。這意味著,雖然針對不同物件的具體操作不同,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以呼叫。

我在《Python 中的設計模式詳解之:策略模式》一文中詳細描述了策略模式的實現,而策略模式就是典型的多型應用。

之前的程式碼我就不貼了,大家可以去原文中檢視。我依然還是以商品折扣的經典舉例。策略模式一文中,傳統的策略模式實現方式我也是用 Python 程式碼實現的,在 java 或 C# 等語言中,實現方式也差不多。以下是 C# 程式碼,我只列了個架子:

interface Promotion
{
    double discount(Order order);
}

class FidelityPromo : Promotion  // 第一個具體策略
{
    // 為積分為1000或以上的顧客提供5%折扣
    public double discount(Order order)
    {
        ...
    }
}

class BulkItemPromo : Promotion // 第二個具體策略
{
    //單個商品為20個或以上時提供10%折扣
    public double discount(Order order)
    {
        ...
    }
}

class LargeOrderPromo : Promotion // 第三個具體策略
{
    //訂單中的不同商品達到10個或以上時提供7%折扣
    public double discount(Order order)
    {
        ...
    }
}
複製程式碼

可以看到,首先要有一個介面(Promotion),然後各個策略去實現這個介面。然而,Python 語言沒有 interface 關鍵字,就是說,Python 裡沒有像 java、C# 一樣的介面。

在策略模式一文的實現中,使用了抽象基類(Abstract Base Class,ABC)來實現介面,這主要是為了寫法上看起來和 java、C# 等語言更加的像,易於有這些語言基礎的同學理解和對比。

抽象基類是在 Python 語言誕生 15 年後,Python 2.6 才引入的。這裡我們不詳細介紹抽象基類,因為即便現在也很少有程式碼使用抽象基類。對於多型,Python 有更好的實現方式——鴨子型別(duck typing)。

協議和鴨子型別

所謂 鴨子型別 就是:如果一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼它就是鴨子。這個概念的名字來源於 James Whitcomb Riley 提出的鴨子測試。

初次看到這個描述的小夥伴一定一頭霧水,為了理解鴨子型別,我們不得不提到另一個名詞——協議。

在物件導向程式設計中,協議是非正式的介面,是一組方法,只由文件和約定定義,因此,協議不能像正式介面那樣施加強制性約束。而 Python 的哲學就是儘量支援基本協議。

翻譯成人話,就是:Python 中沒有介面,在需要使用介面的地方,就用協議代替。所謂協議,其實就是一組方法,和介面中定義的方法一個意思。只不過協議是不是強制性的約定,如果你不遵守協議,那麼也沒關係,執行時報錯就是了。

這樣就好理解鴨子型別了,“如果一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子” 這就表示已經遵守了協議,“那麼它就是鴨子”,意味著你可以在其他用到“鴨子”的地方,用“這隻鳥”替換。這不就是多型嗎?

用“鴨子型別”來實現策略模式也很簡單,刪掉抽象基類就可以了。(這就是為什麼抽象基類很少使用的原因,因為刪掉程式碼也一樣正確啊。)有興趣的小夥伴可以自己嘗試一下程式碼。

Python 中的協議舉例

Python 中有很多的協議,比如迭代器協議,任何實現了 __iter____next__ 方法的物件都可稱之為迭代器,但物件本身是什麼型別不受限制,這得益於鴨子型別。

from collections import Iterable
from collections import Iterator


class MyIterator:
    def __iter__(self):
        pass

    def __next__(self):
        pass


print(isinstance(MyIterator(), Iterable)) 
print(isinstance(MyIterator(), Iterator)) 
複製程式碼

輸出:

True
True
複製程式碼

結語

鴨子型別是程式語言中動態型別語言中的一種設計風格,一個物件的特徵不是由父類決定,而是通過物件的方法決定的。

Python 不是不支援多型,而是 Python 本身就是一門多型的語言。


掃碼關注我的公眾號

大齡碼農的Python之路
Python:多型、協議和鴨子型別

相關文章