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

對例子的解釋

讓我們看看這個例子中具體發生了什麼:

  1. generate_power() 是一個工廠函式,每次呼叫它時,會返回一個新建立的函式,因此,raise_two、raise_three 指向的是這些新建立的函式;
  2. 這些新建立的函式,需要一個引數 power,返回的值是 number**power;
  3. 那麼,這個 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