天天看點

Python 嵌套函數

簡述

在 Python 中,函數的用法可謂是千變萬化,隻不過我們平日接觸的大多是一些基本用法。函數強大之處當然不止于此,它還有很多進階用法 - 閉包、裝飾器。。。

這些進階用法都與嵌套函數緊密相關,是以有必要先熟悉一下嵌套函數。

| 版權聲明:一去、二三裡,未經部落客允許不得轉載。

嵌套函數

嵌套函數(Nested function)是在另一個函數(即:封閉函數)中定義的函數

嵌套函數的基本概念在前面已經介紹過了,參見:​​Python 函數的進階用法​​

那麼,一般在什麼情況下使用嵌套函數?

  • 封裝 - 資料隐藏
  • 貫徹 DRY 原則
  • 閉包

除此之外,嵌套函數還有其他用法,隻不過這些是比較常用的。另外,閉包的内容較為豐富,後面做單獨分享。

封裝 - 資料隐藏

可以使用内層函數來保護它們不受函數外部變化的影響,也就是說把它們從全局作用域隐藏起來。

來看一個簡單的例子 - 求一個數字 n 的倍數:

>>> def outer(n):
...     def inner_multiple(n):  # 從外部代碼隐藏
...         return n * 2
...     num = inner_multiple(n)
...     print(num)
...      

嘗試調用 ​

​inner_multiple(n)​

​ 會引發錯誤:

>>> outer(5)
10
>>> 
>>> inner_multiple(5)  # 外部無法通路
...
NameError: name 'inner_multiple'      

這是因為它被定義在 ​

​outer()​

​ 的内部,被隐藏了起來,是以外部無法通路。

在 ​​Python 遞歸函數​​ 一節中,分享過一個關于階乘的遞歸實作,但是存在一個問題 - 沒有做容錯處理。

下面來利用嵌套函數,對遞歸做一個更好的實作:

>>> def factorial(n):
...
...     # 錯誤處理
...     if not isinstance(n, int):  # 僅限整形
...         raise TypeError("Sorry. 'n' must be an integer.")
...     if n < 0:  # 僅限 0 和 正數
...         raise ValueError("Sorry. 'n' must be zero or positive.")
...     
...     def inner_factorial(n):
...         if n <= 1:
...             return 1
...         return n * inner_factorial(n - 1)
...     return inner_factorial(n)
...      

這樣以來,程式就會變得更加健壯,當資料類型或值不合理時,便會引發錯誤:

>>> factorial('Py')  # 類型錯誤
...
TypeError: Sorry. 'n' must be an integer.
>>> 
>>> factorial(-10)  # 值錯誤
...
ValueError: Sorry. 'n' must be zero or positive.
>>> 
>>> factorial(5)  # OK
120      

使用這種模式的主要優勢在于:利用外部函數執行所有的參數檢查,便可以在内部函數中跳過錯誤檢查,并安全放心地進行使用。

貫徹 DRY 原則

DRY(Don’t Repeat Yourself)- 是指在程式設計以及計算中避免重複代碼,因為這樣會降低靈活性、簡潔性,并且有可能導緻代碼之間的沖突。

DRY 更多的是一種架構設計思想,在軟體開發過程中的萬事萬物均可能重複,大到标準、架構、開發流程;中到元件、接口;小到功能、代碼均純存在自我重複。而 DRY 提倡的就是在軟體開發過程中應消除所有這些自我重複。

如果看過《程式員修煉之道》,想必對這一思想有所了解,那裡面做了很好的闡述。

例如,處理檔案時,需要支援打開的檔案對象和檔案名,傳統方式是采用兩個函數分别實作。

支援檔案對象:

>>> def print_file(f):
...     for line in f:
...         print(line)
... 
>>> f = open('data.txt', 'r')
>>> print_file(f) 
Hi, all

I am a Pythonista

>>>      

支援檔案名:

>>> def print_file_content(file_name):
...     if isinstance(file_name, str):
...         with open(file_name, 'r') as f:
...             for line in f:
...                 print(line)
... 
>>> print_file_content('data.txt')
Hi, all

I am a Pythonista

>>>      

很顯然,有很多代碼可以複用,通過嵌套函數來實作:

>>> def print_file(file):
...     def inner_print(file_process):
...         for line in file_process:
...             print(line, end = '')
...     
...     if isinstance(file, str):
...         with open(file, 'r') as f:
...             inner_print(f)
...     else:
...         inner_print(file)
...      
>>> f = open('data.txt', 'r')
>>> print_file(f)
Hi, all
I am a Pythonista
>>> 
>>> print_file('data.txt')
Hi, all
I am a Pythonista