在正式講「閉包」以前咱們首先得先知道「嵌套函數」這麼一個東西,我在以前的文章中(零基礎學習 Python 之函數對象)說過,函數不僅僅能夠做爲對象來傳遞,還能夠在一個函數裏面嵌套一個函數,這個就是咱們今天要講的嵌套函數。編程
首先咱們來看一個例子:bash
>>> def my_name():
... def your_name():
... print('your_name() is two dog')
... print('my_name() is rocky')
...
複製代碼
上面就是一個簡單的嵌套函數的例子,在上面的代碼中,在函數 my_name()
中定義了函數 your_name()
,而 your_name() 就稱爲 my_name() 的「內嵌函數」,由於它是在 my_name() 裏面的定義。閉包
而後咱們來調用 my_name(),會獲得下面的結果:編程語言
>>> my_name()
my_name() is rocky
複製代碼
這個結果說明在上面的調用方式和內嵌函數的寫法中,your_name() 這個函數根本沒被調用,或者咱們能夠這麼說,那就是 my_name() 沒有按照從上到下的順序依次執行其裏面的代碼。函數式編程
那麼我想要 your_name() 這個內嵌函數也執行,該怎麼作呢?其實在 my_name() 裏面顯示的調用一下 your_name() 函數就行了,請看下面的代碼:函數
>>> def my_name():
... def your_name():
... print('your_name() is two dog')
... your_name() #顯示的調用內嵌函數
... print('my_name() is rocky')
...
複製代碼
咱們如今來調用 my_name(),運行結果以下:學習
>>> my_name()
your_name() is two dog
my_name() is rocky
複製代碼
如今咱們再來思考一個問題,咱們能不能在 my_name() 外面單獨的調用其內嵌函數 your_name() 呢?咱們來試一下:ui
>> your_name()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'your_name' is not defined
複製代碼
結果會顯示錯誤信息,這說明這樣調用是不行的,緣由就是 your_name() 是定義在 my_name() 裏面的函數,它生效的範圍僅限於 my_name() 函數體以內,也就是說它的做用域就是 my_name() 的範圍而已,既然是這樣,那麼 your_name 在使用變量的時候也就會收到 my_name() 的約束。spa
咱們再來看一個例子:.net
>>> def fun1():
... a = 1
... def fun2():
... a += 1
... print('fun2 -- a = ',a)
... fun2()
... print('fun1 -- a = ',a)
...
複製代碼
在看下面的結果以前,請你想想這個函數的結果會是什麼?加入你思考完畢,請看下面的結果:
>>> fun1()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in fun1
File "<stdin>", line 4, in fun2
UnboundLocalError: local variable 'a' referenced before assignment
複製代碼
你猜對了麼?結果是運行錯誤!咱們觀察報錯的信息,緣由是 fun2() 裏面使用了 fun1() 的變量 a,按照表達式, Python 解釋器認爲這個變量應該在 fun2() 中創建,而不是引用 fun1() 中的變量,因此才報錯。
在 Python 中,咱們可使用 nonlocal 這個關鍵詞,具體操做見下例:
>>> def fun1():
... a = 1
... def fun2():
... nonlocal a
... a += 1
... print('fun2 -- a = ',a)
... fun2()
... print('fun1 -- a = ',a)
...
複製代碼
而後咱們調用 fun1() 函數,獲得以下結果:
fun2 -- a = 2
fun1 -- a = 2
複製代碼
綜上所述就是嵌套函數的原理,剩下的就是在實踐中去運用它,達到加深理解的目的。
這個嵌套函數,其實能夠製做動態的函數對象,而這個話題延伸下去,就是所謂的「閉包」。
咱們都知道在數學中有閉包的概念,但此處我要說的閉包是計算機編程語言中的概念,它被普遍的使用於函數式編程。
關於閉包的概念,官方的定義頗爲嚴格,也很難理解,在《Python語言及其應用》一書中關於閉包的解釋我以爲比較好 -- 閉包是一個能夠由另外一個函數動態生成的函數,而且能夠改變和存儲函數外建立的變量的值。乍一看,好像仍是比較很難懂,下面我用一個簡單的例子來解釋一下:
>>> a = 1
>>> def fun():
... print(a)
...
>>> fun()
1
>>> def fun1():
... b = 1
...
>>> print(b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
複製代碼
毋庸置疑,第一段程序是能夠運行成功的,a = 1 定義的變量在函數裏能夠被調用,可是反過來,第二段程序則出現了報錯。
在函數 fun() 裏能夠直接使用外面的 a = 1,可是在函數 fun1() 外面不能使用它裏面所定義的 b = 1,若是咱們根據做用域的關係來解釋,是沒有什麼異議的,可是若是在某種特殊狀況下,咱們必需要在函數外面使用函數裏面的變量,該怎麼辦呢?
咱們先來看下面的例子:
>>> def fun():
... a = 1
... def fun1():
... return a
... return fun1
...
>>> f = fun()
>>> print(f())
1
複製代碼
若是你仔細看過上面文章的內容,你必定以爲的很眼熟,上述的本質就是咱們所講的嵌套函數。
在函數 fun() 裏面,有 a = 1 和 函數 fun1() ,它們兩個都在函數 fun() 的環境裏面,可是它們兩個是互不干擾的,因此 a 相對於 fun1() 來講是自由變量,而且在函數 fun1() 中應用了這個自由變量 -- 這個 fun1() 就是咱們所定義的閉包。
閉包實際上就是一個函數,可是這個函數要具備
上述例子經過閉包在 fun() 執行完畢時,a = 1依然能夠在 f() 中,即 fun1() 函數中存在,並無被收回,因此 print(f()) 才獲得告終果。
當咱們在某些時候須要對事務作更高層次的抽象,用閉包會至關舒服。好比咱們要寫一個二元一次函數,若是不使用閉包的話相信你能夠垂手可得的寫出來,下面讓咱們來用閉包的方式完成這個一元二次方程:
>>> def fun(a,b,c):
... def para(x):
... return a*x**2 + b*x + c
... return para
...
>>> f = fun(1,2,3)
>>> print(f(2))
11
複製代碼
上面的函數中,f = fun(1,2,3) 定義了一個一元二次函數的函數對象,x^2 + 2x + 3,若是要計算 x = 2 ,該一元二次函數的值,只須要計算 f(2) 便可,這種寫法是否是看起來更簡潔一些?
當咱們在後面學習了類的知識之後,再回過頭來看閉包的應用,其實你會有更深的認識,這個咱們在後面再作討論,先知道有類這麼一個概念就行了。
固然閉包在實際的應用中還有不少方面,做爲零基礎入門這個系列咱們就到此爲止,不作深究,可能在後面我會在別的系列中再進一步的講一下,若是你如今對這個方面很感興趣,能夠 Google 一下這方面的文章,有不少的。
更多內容,歡迎關注公衆號「Python空間」,期待和你的交流。