1. Python嵌套函數和閉包
在Python語言中,可以在函數中定義函數。 這種在函數中嵌套定義的函數也叫內部函數。我們來看下面的代碼:
上述代碼中,定義了函數greet,在函數greet內部又定義了一個函數inner_func, 並調用該函數列印了一串字元。
我們可以看到,內部函數inner_func的定義和使用與普通函數基本相同。需要注意的是變數的作用域,在上述代碼中,函數參數name對於全局函數greet是局部變數,對內部函數inner_func來說則是非局部變數。內部函數對於非局部變數的訪問規則類似於標準的外部函數訪問全局變數。
從這個例子我們還可以看到內部函數的一個作用,就是通過定義內部函數的方式將一些功能隱藏起來,防止外部直接調用。常見的場景是,在一個復雜邏輯的函數中,將一些小的任務定義成內部函數,然後由這個外層函數使用,這樣可以使代碼更為清晰,易於維護。這些內部函數只會在這個外層函數中使用,不能被其他函數或模塊使用。
在Python語言中, 函數也是對象,它可以被創建、賦值給變數,或者作為函數的返回值。我們來看下面這個例子。
在上述代碼中,在函數gen_greet內部定義了inner_func函數,並返回了一個inner_func函數對象。外部函數gen_greet返回了一個函數對象,所以像gen_greet這樣的函數也叫工廠函數。
在內部函數inner_func中,使用了外部函數的傳參greet_words(非局部變數),以及函數的參數name(局部變數),來列印一個字元串。
接下來,調用gen_greet("Hello")創建一個函數對象say_hello,緊接著調用say_hello("Mr. Zhang"),輸出的結果為:Hello, Mr. Zhang!
同樣的,調用gen_greet("Hi")創建一個函數對象say_hi,調用say_hello("Mr. Zhang"),輸出的結果為:Hi,Tony!
我們可以發現,gen_greet返回的函數對象具有記憶功能,它能夠把所需使用的非局部變數保存下來,用於後續被調用的時候使用。這種保存了非局部變數的函數對象被稱作閉包(closure)。
那麼閉包是如何實現的呢?其實並不復雜,函數對象中有一個屬性__closure__,它就是在創建函數對象時用來保存這些非局部變數的。
__closure__屬性是一個元組或者None類型。在上述代碼中,我們可以通過下面方式查看:
函數的嵌套所實現的功能大都可以通過定義類的方式來實現,而且類是更加面向對象的代碼編寫方式。
嵌套函數的一個主要用途是實現函數的裝飾器。我們看下面的代碼:
在上述代碼中,logger函數返回函數with_logging,with_logging則是列印了函數func的名稱及傳入的參數,然後調用func, 並將參數傳遞給func。其中的@wraps(func)語句用於復制函數func的名稱、注釋文檔、參數列表等等,使得with_logging函數具有被裝飾的函數func相同的屬性。
代碼中接下來用@logger對函數power_func進行修飾,它的作用等同於下面的代碼:
可見,裝飾器@符其實就是上述代碼的精簡寫法。
通過了解了嵌套函數和閉包的工作原理,我們在使用過程中就能夠更加得心應手了。