python中的閉包從表現形式上定義(解釋)為:如果在一個內部函數裡,對在外部作用域(但不是在全域作用域)的變數進行引用,那麼內部函數就被認為是閉包(closure)。
以下說明主要針對 python2.7,其他版本可能有差異。
也許直接看定義並不太能明白,下面我們先來看什麼叫做內部函數:
def wai_hanshu(canshu_1): def nei_hanshu(canshu_2): # 我在函数内部有定义了一个函数 return canshu_1*canshu_2 return nei_hanshu # 我将内部函数返回出去 a = wai_hanshu(123) # 此时 canshu_1 = 123 print a print a(321) # canshu_2 = 321
#我在函數裡面有嵌套了一個函數,當我向外層函數傳遞一變量的之後,並賦值給a ,我們發現a 變成了一個函數對象,而我再次為這個函數物件傳參的時候,又得到了內部函數的回傳值。我們知道,依照作用域的原則來說,我們在全域作用域是不能存取局部作用域的。但是,這裡透過討巧的方法存取到了內部函數。 。
下面我們繼續看一個例子:
def wai_hanshu(): a = [] def nei_hanshu(canshu): a.append(canshu) return a return nei_hanshu a = wai_hanshu() print a(123) print a(321)
#可以看出函數位於外部函數中的列表a 竟然改變了。要知道為什麼,就要先知道什麼是python的命名空間,而命名空間就是作用域表現的原因,這裡我簡單說明一下。
引入命名空間的主要原因還是為了避免變數衝突,因為python中的模組眾多,模組中有函數,類別等,它們都要使用到變數。但如果每次都要注意不和其他變數名稱衝突,那就太麻煩了,開發人員應該專注於自己的問題,而不是考慮別人寫的程式中用到了什麼變量,所以python引入了命名空間。命名空間分為模組層,模組內又分為全域作用域和局部作用域,用一個圖來表示的話:
模組之間命名空間不同,而裡面還有全域作用域和局部作用域,局部作用域之前還能嵌套,這樣就能保證變數名稱不衝突了。這裡順便補充一下,可以透過__name__ 屬性來取得命名空間的名字:
#主檔案的命名空間是叫做'__main__',而模組的命名空間就是模組名。
作用域的誕生,是因為當python在尋找變數的時候,首先會在目前的命名空間中尋找,如果目前命名空間中沒有,就到上一層的命名空間中找,以此類推,如果最後都沒找到,觸發變數沒找到的異常。
我們之前一直說:全域作用域無法存取局部作用域,而局部作用域能夠存取全域作用域就這這個原因。而當我在局部作用域創建了一個和外面同名的變數時,python在找這個變數的時候首先會在當前作用域中找,找到了,就不繼續往上一級找了。
在早期的python版本時,局部作用域是不能存取其他的局部作用域的,只能存取全域的,而現在的版本都是依序向上一級找,這裡就提一下。
也就是因為這個特性,我們可以在內部函數中存取外部函數中的變量,這也就是所謂的閉包了。
注意:這裡要做好物件之間的區分,例如:
def wai_hanshu(): a = [] def nei_hanshu(canshu): a.append(canshu) return a return nei_hanshu a = wai_hanshu() # 我创建了一个对象 b = wai_hanshu() # 我又创建了一个对象 print a print b print a(123) print b(321)
在這裡,我們雖然都是操作wai_hanshu 中的變量,但是a 和b 完全是兩個對象,它們所在的記憶體空間也是不同的,所以裡面的資料也是獨立的。要注意不要搞混。
裝飾器
其實裝飾器就是在閉包的基礎上多進行了幾步,看程式碼:
#
def zsq(func): # 装饰函数 def nei(): print '我在传入的函数执行之前做一些操作' func() # 执行函数 print '我在目标函数执行后再做一些事情' return nei def login(): # 被装饰函数 print '我进行了登录功能' login = zsq(login) # 我将被装饰的函数传入装饰函数中,并覆盖了原函数的入口 login() # 此时执行的就是被装饰后的函数了#
在看这段代码的时候,要知道几件事:
1.函数的参数传递的其实是引用,而不是值。
2.函数名也是一个变量,所以可以重新赋值。
3.赋值操作的时候,先执行等号右边的。
只有明白了上面这些事之后,再结合一下代码,应该就能明白什么是装饰器了。所谓装饰器就是在闭包的基础上传递了一个函数,然后覆盖原来函数的执行入口,以后调用这个函数的时候,就可以额外实现一些功能了。装饰器的存在主要是为了不修改原函数的代码,也不修改其他调用这个函数的代码,就能实现功能的拓展。
而python觉得让你每次都进行重命名操作实在太不方便,于是就给出了一个便利的写法:
def zsq(func): def nei(): print '我在传入的函数执行之前做一些操作' func() # 执行函数 print '我在目标函数执行后再做一些事情' return nei @zsq # 自动将其下面的函数作为参数传到装饰函数中去 def login(): print '我进行了登录功能' login()
这些小便利也叫做python的语法糖,你可能在很多地方见过这个说法。
带参数的装饰器:
def zsq(a): print '我是装饰器的参数', a def nei(func): print '我在传入的函数执行之前做一些操作' func() # 执行函数 print '我在目标函数执行后再做一些事情' return nei @zsq('123') def login(): print '我进行了登录功能'
相当于: login = zsq(123)(login) ,所以在这里没有调用就执行了。
装饰器的嵌套:
这里就不完整写个例子了:
@deco1(deco_arg) @deco2 def func(): pass
相当于: func = deco1(deco_arg)(deco2(func))
也就是从上到下的嵌套了。
关于闭包和装饰器就先讲到这里,以后有需要再补充。
以上这篇深入理解python中的閉包和裝飾器就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持PHP中文网。
更多理解python中的閉包和裝飾器相关文章请关注PHP中文网!