零基礎學習 Python 之閉包

寫在以前

在正式講「閉包」以前咱們首先得先知道「嵌套函數」這麼一個東西,我在以前的文章中(零基礎學習 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() 就是咱們所定義的閉包。

閉包實際上就是一個函數,可是這個函數要具備

  • 1.定義在另一個函數裏面(嵌套函數);
  • 2.引用其所在環境的自由變量。

上述例子經過閉包在 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空間」,期待和你的交流。

在這裏插入圖片描述
相關文章
相關標籤/搜索