在正式講「閉包」之前我們首先得先知道「巢狀函式」這麼乙個東西,我在之前的文章中(零基礎學習 python 之函式物件)說過,函式不單單可以作為物件來傳遞,還可以在乙個函式裡面巢狀乙個函式,這個就是我們今天要講的巢狀函式。
首先我們來看乙個例子:
>>> 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() 呢?我們來試一下:
>> your_name()
traceback (most recent call last):
file "", line 1, in
nameerror: name 'your_name' is not defined
複製**
結果會顯示錯誤資訊,這說明這樣呼叫是不行的,原因就是 your_name() 是定義在 my_name() 裡面的函式,它生效的範圍僅限於 my_name() 函式體之內,也就是說它的作用域就是 my_name() 的範圍而已,既然是這樣,那麼 your_name 在使用變數的時候也就會收到 my_name() 的約束。
我們再來看乙個例子:
>>> def fun1():
... a = 1
... def fun2():
... a += 1
... print('fun2 -- a = ',a)
... fun2()
... print('fun1 -- a = ',a)
...複製**
在看下面的結果之前,請你想一想這個函式的結果會是什麼?加入你思考完畢,請看下面的結果:
>>> fun1()
traceback (most recent call last):
file "", line 1, in
file "", line 6, in fun1
file "", line 4, in fun2
unboundlocalerror: local variable 'a' referenced before assignment
複製**
你猜對了麼?結果是執行錯誤!我們觀察報錯的資訊,原因是 fun2() 裡面使用了 fun1() 的變數 a,按照表示式, python 直譯器認為這個變數應該在 fun2() 中建立,而不是引用 fun1() 中的變數,所以才報錯。
>>> 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 "", line 1, in
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零基礎入門之函式閉包
函式的global 全域性變數 包含內建和全域性命名空間的所有全域性作用域資料 只有在區域性中宣告了全域性變數 這樣在區域性進行的修改同樣作用於全域性 nonlocal 對於全域性變數無效 只作用於就近一層的區域性變數 globals 返回是全域性中的內容 locals 返回的是本地的內容 本地是全...
零基礎學習 Python 之元組
元組和我們之前講過的數字,字串和列表一樣,也是 python 中的一種物件型別,它和列表比較想象,所以一般會被忽略掉,但是由於其特殊性,在實際應用的時候又很容易犯錯。首先我們來看乙個例子 a abc a abc b def 123,1,2,3 b def 123,1,2,3 type a str t...
零基礎學習 Python 之字典
相信大家對字典並不陌生吧,學生時代一塊塊的大小 磚頭 還記得怎麼用吧?先從索引查詢,不管是拼音還是偏旁部首,通過索引查到相應的內容,這樣就不需要我們從頭一頁一頁的找,這種方法可以很快的找到目標。正是基於這種需求,python 裡有了一種 dictionary 的物件型別,翻譯過來就是 字典 用 di...