1. 程式人生 > >最簡潔的解釋動態語言中的鴨子型別和閉包

最簡潔的解釋動態語言中的鴨子型別和閉包

常見定義

  • 閉包

是擁有獨立變數(在封閉空間中定義的可以在本地環境中使用的變數)的函式

程式語言中的閉包(closure)概念不是由JavaScript最先提出的,從smalltalk開始,閉包就成了程式語言的一個重要概念。幾乎所有的知名動態語言(如Perl、python、ruby等)都支援閉包,JavaScript也不例外。

  • 鴨子型別

是程式設計中的一種型別推斷風格,這種風格適用於動態語言(比如PHP、Python、Ruby、Typescript、等)和某些靜態語言(比如Golang,一般來說,靜態型別語言在編譯時便已確定了變數的型別,但是Golang的實現是:在編譯時推斷變數的型別),支援"鴨子型別"的語言的直譯器/編譯器將會在解析(Parse)或編譯時,推斷物件的型別。

鴨子型別並不是動態語言特有,但是多數動態語言都帶有這個推斷風格。

簡潔解釋

  • 閉包

以python為例,如果你見到的程式碼和下面的模式大概一樣,那麼就可以推定是運用了閉包了:

def adder(x):
    def wrapper(y):
        return x + y
    return wrapper

adder5 = adder(5)
# 輸出 15
adder5(10)
# 輸出 11
adder5(6)

一般情況下,函式執行完之後其內部變數將會不可被訪問,但是為什麼adder5(10)會輸出15,而adder5(6)會輸出11呢?按理說應該分別輸出20和12的,可是adder(5)中的變數5卻一直保留著。

劃重點:一般情況下,函式執行完了之後內部變數就會被銷燬(垃圾回收機制),但如果函式執行完了,內部變數還有用,那就不能被銷燬,既然不能銷燬那就得儲存起來,正好函式也是物件,物件具有儲存資料的能力,於是就把這個不能銷燬的變數存在函式裡了。這種函式儲存變數的屬性就叫閉包。

  • 鴨子型別

還是以python為例,鴨子型別其實是一種編譯器\直譯器的推斷風格,就是你敲的程式碼,執行時直譯器是如何識別的,其中有一種識別的模式是鴨子型別。

class A:
    def __init__(self, filename):
        if not filename.endswith(self.ext):
            raise Exception("Invalid file format")
        self.filename = filename

class B(A):
    ext = "banana"
    def play(self):
        print("Playing {} as banana".format(self.filename))

class C(A):
    ext = "captcha"
    def play(self):
        print("Playing {} as captcha".format(self.filename))

class A1:
    def __init__(self, filename):
        if not filename.endswith(".flac"):
            raise Exception("Invalid file format")
        self.filename = filename

    def play(self):
        print("Playing {} as flac".format(self.filename))

咋一看,上面的B、C都繼承了A,那麼他們完全可以隨意呼叫A中定義的任何元素,但是,仔細一看,A1和A除了名字不同,其他都是一模一樣的,那麼B、C在只繼承A的情況下,能否呼叫A1呢?答案是可以的!

劃重點:所謂鴨子型別,就是隻要你跟我的功能和實現的結構一模一樣,就算名字不同,我們也是一樣的物件。應徵了“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子”這句話。

通俗解釋就是:當你要重寫一個class類A,它的原型是class類B,只要A中實現的方法在B中也有,那麼無論def方法內容如何,你不必用A繼承B,直譯器也會知道你重寫的類是繼承B的。