簡述
在 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