Python重難點總結——閉包
來自專欄大數據轉行學習筆記4 人贊了文章
閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函數。
先說說全局變數和局部變數:
定義在模塊最外層的變數是全局變數,在函數裡面也可以讀取到全局變數
局部變數在函數外部則不可以訪問
對於嵌套函數,它可以訪問到其外層作用域中聲明的非局部變數,在閉包中,即便脫離了函數本身的作用範圍,局部變數還可以被訪問得到。
閉包中為什麼能訪問局部變數呢?
那是因為所有函數都有一個 __closure__
屬性,如果這個函數是一個閉包的話,那麼它返回的是一個由 cell 對象 組成的元組對象。cell 對象的cell_contents 屬性就是閉包中的自由變數。局部變數脫離函數之後,還可以在函數之外被訪問的原因的就是它已經存儲在了閉包的cell_contents中了。
我們來看一個例子:
def func(num): print(hello ) def func_in(): print(the world) print(1000*num) print(!) return func_ina=func(2)a()
在一個函數func()中定義了另一個函數func_in( ),調用func()函數之時函數func_in()並沒用被調用,但返回的是func_in()函數的引用。
然後將func()調用後賦值給a,a相當於得到了func(2)函數調用後返回的func_in函數的引用,a()相當於func_in( )來調用func_()函數
輸出結果為:
這段代碼有以下的特徵:
1.func函數里內嵌了一個func_in函數
2.func_in函數使用了外部函數func中的num變數
3.外部函數func最後返回了func_in函數
閉包的特徵就是:
1.閉包函數必須有內嵌函數
2.內嵌函數需要引用該嵌套函數上一級namespace中的變數
3.閉包函數必須返回內嵌函數
所以,上述函數就是一個閉包。
那麼閉包有什麼作用呢?我們再來看一個例子理解下:
def grade(str1): print(你的成績是:) def func_in(str2): print(str1+str2) return func_ina=grade(小李:)a(語文118)b=grade(小紅:)b(語文:109)b(數學:98)
使用閉包的方法能很好地將不同對象用同一個函數處理後的數據整理歸類。
閉包避免了使用全局變數,此外,閉包允許將函數與其所操作的某些數據(環境)關連起來。這一點與面向對象編程是非常類似的,在面對象編程中,對象允許我們將某些數據(對象的屬性)與一個或者多個方法相關聯。
請注意我們每次調用時會返回一個新函數,即使參數相同id地址也不相同。
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sumf1 = lazy_sum(1, 3, 5, 7, 9)f2 = lazy_sum(1, 3, 5, 7, 9)f1==f2
輸出的結果是False,f1和f2不相互影響。
另外我們需要特別注意,返回的函數並沒有立刻執行,而是直到調用了f()
才執行,來看一道經典的python面試題:
def testFun(): temp = [lambda x : i*x for i in range(4)] return tempfor everyLambda in testFun(): print (everyLambda(2))
testFun()調用時會依次返回0*x,1*x,2*x,3*x
那麼everyLambda(2)會依次用2帶入testFun()的調用中,結果應該是0,2,4,6,我們來看調用結果:
原因就在於返回的函數引用了變數i
,但它並非立刻執行。等到4個函數都返回時,它們所引用的變數i
已經變成了3*x,因此最終結果為4個6
那麼如果一定要引用循環變數怎麼辦?方法有很多種:
1.通過使用默認參數立即綁定它的參數:
def testFun(): temp = [lambda x ,i=i: i*x for i in range(4)] return tempfor everyLambda in testFun(): print (everyLambda(2))
2.使用functools.partial
函數,把函數的某些參數(不管有沒有默認值)給固定住(也就是相當於設置默認值)
from functools import partial from operator import mul def testFun(): return [partial(mul,i) for i in range(4)]for everyLambda in testFun(): print (everyLambda(2))
3.直接用生成器,利用yield的惰性求值的思想
def testFun(): for i in range(4): yield lambda x : i*xfor everyLambda in testFun(): print (everyLambda(2))
一般來說,當對象中只有一個方法時,使用閉包是非常好的選擇,此外裝飾器也是基於閉包的一中應用場景。
推薦閱讀: