1. 程式人生 > >簡簡單單說個閉包

簡簡單單說個閉包

無法 服務 AR 編程語言 函數式 outer 但是 min 概念

閉包的作用

一句話,閉包的作用:將方法存於變量。

至於閉包的原因或者目的,或者說,為什麽將方法存於變量,稍後再說。

閉包的條件

為了盡量避免用一大段話描述一個概念,我們理性一點地把閉包的條件劃分成3個:

  1. 外函數中定義了一個內函數
  2. 內函數用了外函數的變量
  3. 外函數返回了內函數的引用,or,外函數中直接調用了內函數

P.S.

  1. 其中外函數和內函數是指嵌套函數中外部函數和內部函數
  2. 也正是因為需要嵌套函數,因此不支持的嵌套函數的語言也自然不支持此類閉包
  3. 條件3中分成了兩類,更多的情況下是前一類,而後一類(外函數直接調用了內函數)的使用更多的是為了保證代碼的簡潔,而如此地保持簡潔並不一定用閉包。

閉包的例子

“Talk is cheap, show me your code.”

我始終覺得,在編程中,過多的人類語言會產生太多的歧義,甚至還可能會因為所說事物過於抽象而導致聽眾無法將概念理解。

而解決這個問題最好的方法就是看代碼,編程語言相較於人類語言的優點之一是大幅地降低了語言的歧義。同時,通過多個代碼實例,人腦會自然而然地將多實例中的共同點提取出來,進而理解抽象的概念。

結合閉包的三個條件,我們來看看閉包的例子:

def outer(b):
    def inner(a):  # 條件1
        print(a + b)  # 條件2
    return inner  # 條件3

# 調用
o = outer(1)
o(2)

Python的代碼還是挺簡單的。

技術分享圖片

一般情況下,在函數結束後,函數中變量等就應該被銷毀,偏偏這個閉包就是個特例 —— o和o2中的1和20都保留著。

o和o2看起來就有那麽一絲熟悉的感覺,它們兩個就像是兩個對象 —— 這兩個“對象”都是從同一個“類”出來的,而兩個“對象實例”的區別是有一個加數不一樣,分別是1和20(當然,這兩個變量的引用地址也不同)。

現在,我們把代碼例子中的第三個條件變一下,即將“外函數返回了內函數的引用”變成“外函數中直接調用了內函數”:

def outer(a, b):
    def inner(a):  # 條件1
        print(a + b)  # 條件2
    inner(b)  # 條件3

# 調用
o = outer(1001)

技術分享圖片

此時,整個閉包函數調用起來就和一個普通函數一樣,傳入兩個參數,該print的也如期而至。

只能說,在outer函數內的邏輯過於復雜的時候,inner能把復雜的代碼“模塊化”,再調用,能增加簡潔性。這種情況下,一般是inner函數只被調用一次,而且只在這裏調用,放在這裏也好管理一些。

接下來,我們也鑒賞一下別的語言類似的閉包:

func outer(i int) func() int {
    return func() int {  // 條件1(匿名)+ 條件3
        i++  // 條件2
        return i
    }
}

// 調用
o := outer(1)
o()

這個Golang的例子閉包和Python的例子較大的距別是這裏還把內函數換成了匿名的,看起來會爽點。而以下的php的就和Python的差不多了。

function outer($str1) {
    $outerStr = $str1;
    $inner = function($str2) {  // 條件1
        echo $str2 . $outerStr;  // 條件2
    };
    return $inner;  // 條件3
}

// 調用
$o = outer("hahaha");
$o("emmm");

閉包的原因

看過了上面的例子後,新手對閉包的概念也應該有了一定的理解,甚至有點想法了。

回到最開始的問題,即閉包的原因。

如果說,“將方法存於變量”是閉包的目的,那麽接下來的問題顯而易見:為什麽要將方法存於變量?直接調用方法(函數)不好嗎?

閉包保存了函數的狀態信息

再舉開頭的例子:

def outer(b):
    def inner(a):
        print(a + b)
    return inner

o = outer(1)
o(2)  # 3
o(100)  # 101
o2 = outer(20)
o2(100)  # 120

o這個變量對應的閉包保存了b=1這個信息,之後無論是調用o(2)還是o(100)b=1這個信息依然會存在並和後來的參數一起參與運算。同理,o2這個變量對應的閉包保存了b=20這個信息。

由於退出了函數後,函數並沒有並銷毀,這個閉包的信息也沒銷毀,因此後續可以利用這些信息。

語法糖

為了代碼的簡潔性和易理解性,我們經常會使用甚至創造一些語法糖。

而在Python中,有一個十分好看的例子就是裝飾器,舉個已經被用爛了的例子,面向切面的登錄實現:

# 先實現一個類似於裝飾器的函數
def decorator(func): 
    def inner():
        print ‘before function‘
        func()  # function
        print ‘after function‘
    return inner

# 實現一個假裝在登錄的登錄函數
def login():  
    print ‘login function complete.‘

# 將登錄函數“套上”裝飾器
login = decorator(login)  
login()

整個過程下來與AOP類似,而場景也很常用,如統計函數的運行時常、加入日誌、統一的過濾處理等等。

更有甚者,在特定的場景下使用閉包創造語法糖,以簡化代碼,參考這個例子,該例子可以替代switch(不過這裏這麽簡單的加減運算這樣寫就很智障了,要有一定的復雜度就能顯得有優越性):

def operator(o):
    def plus(x, y):
        print(x + y)
    def minus(x, y):
        print(x - y)

    if o == ‘+‘:
        return plus
    if o == ‘-‘:
        return minus

def f(x, o, y):
    operator(o)(x, y)

函數式編程

鼎鼎大名的Lambda,這個可以參考下這個鏈接。

總結 廈門叉車服務公司

閉包能將方法存於變量,且實現一些美妙的東西。

它就像是調味劑,並非不可或缺,但是能錦上添花。

先這樣吧

簡簡單單說個閉包