Python旅途休憩——闭包
我要深刻反思一下,為什么最近幾期的blog顯得有一點點水而且風格詭譎了
都怪我找的那個Python網課,只開車不好好講課!
所以深夜加班,重點談一談這個非常重要的內容——閉包
(嘗試通過這次的講解把Python函數部分的知識點串講一ha~)
鳴謝alpha_panda大大~ 和 HAPPYEVERYD大大~
前輩的講解都很深入詳細,幫助我更好地理解這個難點啦
之前在blog中簡單的提到了,閉包并不只是一個Python中的概念,ta在函數式編程語言中應用較為廣泛
理解Python中的閉包一方面是能夠正確的使用閉包,另一方面則可以好好體會和思考閉包的設計思想(有種和前輩心靈相通的感jio)
閉包到底是什么?
首先看一下維基上對閉包的解釋:
在計算機科學中,閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
看不懂吧?巧了,我也看不懂。。。
我們之前解釋了內(嵌)函數的意義,一言以蔽之:如果在一個函數的內部定義了另一個函數(注意一定是定義了一個船新的函數,不是簡單的調用),外部的我們叫他外函數,內部的我們叫他內(嵌)函數。
一般我們對編程語言中函數的理解,大概是這樣的:
- 程序被加載到內存執行時,函數定義的代碼被存放在代碼段中。
- 函數被調用時,會在棧上創建其執行環境,初始化其中定義的變量和外部傳入的形參以便函數進行下一步的執行操作。
- 當函數執行完成并返回函數結果后,函數棧幀便會被銷毀掉。函數中的臨時變量以及存儲的中間計算結果都不會保留。
- 下次調用時唯一發生變化的就是函數傳入的形參可能會不一樣。函數棧幀會重新初始化函數的執行環境。
而閉包已經不再是傳統意義上定義的函數
閉包是一個內嵌函數,其定義中 引用了函數外定義的變量 ,并且該函數 可以在其定義環境外被執行
通俗來講:我們在某個函數(外函數)中又定義了一個函數(內函數),內函數里運用了外函數的臨時變量,并且外函數的返回值是內函數的引用
閉包長什么樣?
下面這段偽代碼展示了閉包的基本結構:
def 外層函數(參數):def 內層函數():print("內層函數執行", 參數)return 內層函數內層函數的引用 = 外層函數("傳入參數") 內層函數的引用()閉包特點之一 . 外函數返回的是內函數的引用
引用是什么?
我們可以簡單的把引用理解成地址(或者指向變量的指針?)
當我們在Python中定義一個函數def demo():的時候,內存當中會開辟一些空間,存下這個函數的代碼,內部的局部變量等等
然而demo其實是一個變量名稱,里面存了這個函數所在位置的引用
我們可以令x = demo
這就相當于把demodemodemo里存的東西賦值給xxx,這樣xxx就指向了demodemodemo函數所在的引用
之后我們可以用x()x()x()來調用我們創建的demo()demo()demo()
(用我自己的理解來說,xxx就是函數demodemodemo的一個別名)
對于閉包,在外函數outer最后return inner
于是我們在調用外函數的時候就會返回inner
inner是一個函數的引用,這個引用被存入了外函數outer中
所以接下來在調用外函數outer的時候,相當于運行了內函數inner
至此,我們有一個重要的發現:
如果函數名后緊跟一對括號,一般意味著調用此函數
如果缺省括號,相當于只是一個函數名稱,里面存儲了函數所在位置的引用
閉包特點之二 . 外函數會將臨時變量綁定給內函數
按照我們正常的認知,一個函數結束的時候,會把自己的臨時變量都釋放
但是閉包是一個特別的情況:
外函數的臨時變量會在將來的內函數中用到(閉包的特性)
于是在外函數結束,返回內函數的同時,會將外函數的臨時變量與內函數綁定在一起
所以盡管外函數已經結束了,調用內函數的時候仍然能夠使用外函數的臨時變量
下面的話就有一些晦澀難懂了:
我們只會定義一次內函數,但是在重復調用的時候,內部函數是能識別外函數的臨時變量是不同的
Python中一切都是對象
雖然函數我們只定義了一次,但是外函數在運行的時候,實際上是按照里面代碼執行的
我們每次調用外函數,都會創建一個內函數,雖然代碼一樣,但是卻創建了不同的對象,并且把每次傳入的臨時變量數值綁定給內函數,再把內函數引用返回
所以實際上我們每次調用外函數時,返回的都是不同的實例對象的引用,ta們的功能是一樣的,但是并不是同一個函數對象
閉包實戰!
通過實戰,我們理解一下之前的泡泡茶壺
def outer(x):def inner(y):return x + yreturn inner>>> f=outer(7) >>> f(2) 9 >>> f(5) 12 >>> g=outer(3) >>> g(5) 8 >>> g(9) 12 >>> outer(4)(5) 9注意!
外函數返回的是內函數的引用,沒有括號!沒有括號!沒有括號!
我現在感覺閉包真是一個優美異常的結構
(當然我給出的是一個不能再簡單的憨憨栗子)
有兩種正確的調用姿勢:
- 把內函數引用賦值給一個輔助變量,這個時候傳入的參數就會直接綁定給內函數,之后只要調用這個輔助變量,就是默認該參數
- 雙括號:外函數名稱(外函數參數)(內函數參數)
好像還有點問題?
如何在閉包中修改外函數局部變量?
在閉包內函數中,我們可以隨意使用外函數綁定的臨時變量,但是如果我們想修改外函數臨時變量數值的時候就出事了。。。
在基本的python語法當中,一個函數可以隨意讀取全局數據,但是想要修改時有兩種方法
在閉包內函數也是類似的情況:
在Python3中,可以用nonlocal關鍵字聲明變量, 表示這個變量不是局部變量空間的變量,需要向上一層變量空間尋找這個變量
(在Python2中,沒有nonlocal這個關鍵字,我們可以把閉包變量改成可變類型數據進行修改,比如列表)
def outer(x):def inner(y):nonlocal xx*=xreturn x + y*yreturn inner>>> f = outer(3) >>> f(4) 25閉包有什么用處?
-
裝飾器&&單利模式
舉個栗子:我們編寫了一個登錄功能,想統計這個功能執行花了多長時間。我們可以用裝飾器裝飾這個登錄模塊,裝飾器會幫我們完成登錄函數執行之前和之后取時間。 -
面向對象
之前提到:外函數會將臨時變量綁定給內函數。
閉包是實現面向對象的方法之一。雖然我們在python中不這樣使用,但是在其他編程語言(比如JavaScript)中,經常用閉包來實現面向對象編程。
總結
以上是生活随笔為你收集整理的Python旅途休憩——闭包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员的“青春饭”能吃多久?IT行业的“
- 下一篇: Simulink/Carsim联合仿真,