Python的功能(python用處大嗎)
這是一篇譯文,原文地址:
https://realpython.com/inner-functions-what-are-they-good-for/
1. 封裝
內部函式可以免受函式之外的情況的影響,也就是說,對於全域性名稱空間而言,它們是隱藏的。
下面是一個簡單的例子:
def outer(num1): def inner_increment(num1): # 對外部空間隱藏 return num1 1 num2 = inner_increment(num1) print(num1, num2) inner_increment(10)# outer(10)
如果我們直接呼叫 inner_increment() 函式,會有報錯資訊:
Traceback (most recent call last): File "inner.py", line 7, ininner_increment()NameError: name 'inner_increment' is not defined
註釋掉對 inner_increment() 的直接呼叫,對外部的函式傳入引數 10,即 outer(10) 是可以執行的:
10 11
注意:這只是一個例子,雖然這些程式碼可以運作,但就這個函式而言,可能更好的方式是把 inner_increment() 定義為存在於外部空間的“私有”函式,即在函式名前加一個下劃線字首,即 _inner_increment() 。
下面這個巢狀函式可能是一個更好的使用內部函式的例子:
def factorial(number): # 處理錯誤 if not isinstance(number, int): raise TypeError("Sorry. 'number' must be an integer.") if not number >= 0: raise ValueError("Sorry. 'number' must be zero or positive.") def inner_factorial(number): if number <= 1: return 1 return number*inner_factorial(number-1) return inner_factorial(number) # 呼叫外部函式 print(factorial(4))
在這裡,我們把引數驗證放在外部函式,而在內部函式中處理關鍵步驟。
2. 避免自我重複(DRY原則)
有時,我們可能會在一個大型函式中,重複地使用一些程式碼。比方說,我們寫一個處理檔案的函式,同時支援檔名或檔案物件作為引數:
def process(file_name): def do_stuff(file_process): for line in file_process: print(line) if isinstance(file_name, str): with open(file_name, 'r') as f: do_stuff(f) else: do_stuff(file_name)
注意:再次提醒,可能更常見的情況是,我們直接把 do_stuff() 放在外部,作為一個私有函式,但顯然,必要時我們也可以把它作為內部函式隱藏起來。
我們可以寫一個更具體的例子。
假如說,我們想了解紐約市的 WIFI 熱點資料,可以直接在網上下載對應的 CSV 檔案,然後進行統計:
def process(file_name): def do_stuff(file_process): wifi_locations = {} for line in file_process: values = line.split(',') # 建立一個字典,記錄統計資料 wifi_locations[values[1]] = wifi_locations.get(values[1], 0) 1 max_key = 0 for name, key in wifi_locations.items(): all_locations = sum(wifi_locations.values()) if key > max_key: max_key = key business = name print(f'紐約市總共有 {all_locations} 個 WIFI 熱點,' f'{business} 提供的熱點最多,有 {max_key} 個。') if isinstance(file_name, str): with open(file_name, 'r') as f: do_stuff(f) else: do_stuff(file_name)
執行後得到結果如下:
>>> process('NAME_OF_THE.csv') 紐約市總共有 1251 個 WiFi 熱點,Starbucks 提供的熱點最多,有 212 個。
3. 閉包與工廠函式
接下來我們要討論的是使用內部函式最重要的理由。在之前的例子中,內部函式都是一個常規函式,只是恰好被巢狀在另一個函式中而已。也就是說,我們完全用其它方式定義它們(如之前已經提示的),並非一定要使用內部函式。
而在考慮閉包的時候,我們就必須使用巢狀函式了。
什麼是閉包
閉包可以使內部函式記住它所在空間的具體狀態。新手們常常以為內部函式就是閉包,準確地說,應該是內部函式製造了閉包。所謂閉包,所“封閉”的是函式幀中的區域性變數。
一個例子
以下是一個例子:
def generate_power(number): """ Examples of use: >>> raise_two = generate_power(2) >>> raise_three = generate_power(3) >>> print(raise_two(7)) 128 >>> print(raise_three(5)) 243 """ # 定義內部函式 def nth_power(power): return number ** power # 將函式作為外部函式的結果返回 return nth_power
對例子的解釋
讓我們看看這個例子中具體發生了什麼:
- generate_power() 是一個工廠函式,每次呼叫它時,會返回一個新建立的函式,因此,raise_two、raise_three 指向的是這些新建立的函式;
- 這些新建立的函式,需要一個引數 power,返回的值是 number**power;
- 那麼,這個 number 的值是怎麼來的呢?這就是閉包發生作用的地方:nth_power() 函式是從外部函式,即工廠函式獲取 number 的值的。整個過程可以分解步驟如下:
- 呼叫外部函式:generate_power(2);
- 建立函式 nth_power(),它需要一個引數 power;
- 儲存 nth_power() 函式幀的狀態,其中包括 number=2;
- 將儲存的函式幀狀態傳遞給 generate_power() 函式;
- 返回 nth_power() 函式;
換句話說,閉包為 nth_power() 函式提供了初始化資料並將它返回。因此,我們呼叫這個被返回的函式時,總是可以在其函式幀中找到 number=2。
一個實際應用
現在,讓我們考慮一個真實世界中的例子:
def has_permission(page): def inner(username): if username == 'Admin': return "'{0}' does have access to {1}.".format(username, page) else: return "'{0}' does NOT have access to {1}.".format(username, page) return inner current_user = has_permission('Admin Area') print(current_user('Admin')) random_user = has_permission('Admin Area') print(random_user('Not Admin'))
這是一個簡化版的許可權判斷函式,我們也可以做簡單修改,從 session 中獲取使用者資訊,進而判斷這個使用者是否具有接入某個路由的許可權。顯然,我們會從資料庫中查詢使用者許可權,而不是檢查使用者名稱是否等於 'Admin'。
總結
閉包與函式工廠是內部函式最常見、最主要的用處。大多數情況下,如果你看到一個帶裝飾器的函式,這個裝飾器就是一個函式工廠,它以一個函式作為引數,並返回一個新的函式,新的函式使用閉包包括了作為引數的函式。
換句話說,裝飾器就是一個語法糖,它的基本流程其實和上面所舉的 generate_power() 的例子是一致的。
以下是最後一個例子:
def generate_power(exponent): def decorator(f): def inner(*args): result = f(*args) return exponent**result return inner return decorator @generate_power(2) def raise_two(n): return n print(raise_two(7)) @generate_power(3) def raise_three(n): return n print(raise_two(5))
如果你的程式碼編輯器允許的話,可以嘗試把 generate_power(exponent) 和 generate_power(number) 並排對比,以理解我們所討論的概念。(比如說,可以用 Sublime Text 中的分欄功能)
如果你還沒寫出這兩個函式的話,建議還是親自在編輯器中敲出來一次,對程式設計新手來說,寫程式碼就行騎自行車:你必須親自上手。
敲出這些程式碼後,你就能看出,它們產生了類似的結果,但也有一些不同。對於還沒有用過裝飾器的人來說,注意到這些不同,就是理解它們的開始。
END
公眾號:ReadingPython