閉包:
閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體
1、函式是一個物件
2、函式執行完成後內部變數回收
3、函式屬性
4、函式的返回值
例項一、
分別檢測分數科目總分為100、150兩種情況的成績
初級程式碼如下:
# -*- coding:utf-8 -*- ###檢測分數總數為100分的及格情況 def ck_100(val): passline = 60 if val >= passline: print "當前科目總分為100,你的成績為: %d,成績通過 " %val else: print "當前科目總分為100,你的成績為: %d,成績不通過 " %val ###檢測分數總數為150分的及格情況 def ck_150(val): passline = 90 if val >= passline: print "當前科目總分為150,你的成績為: %d,成績通過 " %val else: print "當前科目總分為100,你的成績為: %d,成績不通過" %val #100總分科目 ck_100(90) ck_100(55) #150總分科目 ck_150(110) ck_150(88)
執行結果如下:
>>> ================================ RESTART ================================ >>> 當前科目總分為100,你的成績為: 90,成績通過 當前科目總分為100,你的成績為: 55,成績不通過 當前科目總分為150,你的成績為: 110,成績通過 當前科目總分為100,你的成績為: 88,成績不通過 >>>
使用閉包優化後的程式碼:
# -*- coding:utf-8 -*- ###根據不同科目設定及格線 def set_passline(passline): ###檢測傳入分數是否及格 def cmp(val): if val >= passline: print "及格線為: %d, 您的分數為: %d ,恭喜及格" %(passline,val) else: print "及格線為: %d, 您的分數為: %d ,遺憾不及格" %(passline,val) return cmp #總分為100的科目 score_100 = set_passline(60) score_100(89) score_100(55) #總分為150的科目 score_150 = set_passline(90) score_150(99) score_150(88)
執行結果:
>>> ================================ RESTART ================================ >>> 及格線為: 60, 您的分數為: 89 ,恭喜及格 及格線為: 60, 您的分數為: 55 ,遺憾不及格 及格線為: 90, 您的分數為: 99 ,恭喜及格 及格線為: 90, 您的分數為: 88 ,遺憾不及格
Tips:
可以看程式碼量少了一半;
閉包就是我們內建函式對 enclosing 作用域 變數 的使用
例項二、
# -*- coding:utf-8 -*- ###求成績總分 def my_sum(*args): return sum(args) ###求成績平均分 def my_average(*args): return sum(args)/len(args) print my_sum(1,2,3,4,5) print my_average(1,2,3,4,5)
執行結果:
>>> ================================ RESTART ================================ >>> 15 3 >>>
發現上述程式碼不夠健壯,如果你傳入字串或空的列表就會報錯
print my_sum(1,2,3,4,5,`6`)
return sum(args) TypeError: unsupported operand type(s) for +: `int` and `str` >>>
修改程式碼,進行檢查傳入資料的型別及長度
# -*- coding:utf-8 -*- ###求成績總分 def my_sum(*args): if len(args) == 0: return 0 for val in args: if not isinstance(val,int): print "傳入引數有非整數性..." return 0 return sum(args) ###求成績平均分 def my_average(*args): if len(args) == 0: return 0 for val in args: if not isinstance(val,int): print "傳入引數有非整數性..." return 0 return sum(args)/len(args) print my_sum(1,2,3,4,5) print my_average(1,2,3,4,5) print my_sum(1,2,3,4,5,`6`)
執行結果:
>>> ================================ RESTART ================================ >>> 15 3 傳入引數有非整數性... 0
健壯性是可以了,不過發現其中有一部門程式碼是重複的,但是執行的 函式 又是不同的,分別為 my_sum 、my_average
使用閉包優化後的程式碼:
# -*- coding:utf-8 -*- ###求成績總分 def my_sum(*args): return sum(args) ###求成績平均分 def my_average(*args): return sum(args)/len(args) def dec(func): def in_dec(*args): if len(args) == 0: print "傳入引數列表為0" return 0 for val in args: if not isinstance(val,int): print "傳入非整數引數" return 0 return func(*args) return in_dec #重新定義my_sum my_sum = dec(my_sum) #此處呼叫dec,作用就是將 引數為函式的 func 變為 in_dec 函式的一個屬性 ( enclosing 屬性 ),並且將 in_dec 函式本身返回,返回的函式還具有 in_dec 本身的功能屬性 (檢測引數的作用) #並且 my_sum 進行了重新定義,返回值為 傳入函式處理後的 返回值 print my_sum(1,2,3,4,5) print my_sum() print my_sum(1,2,3,4,5,`6`) #重新定義my_average my_average = dec(my_average) print my_average(1,2,3,4,5) print my_average() print my_average(1,2,3,4,5,`6`)
執行結果:
>>> ================================ RESTART ================================ >>> 15 傳入引數列表為0 0 傳入非整數引數 0 3 傳入引數列表為0 0 傳入非整數引數 0 >>>
裝飾器:
1、用來裝飾函式
2、返回一個新的函式物件
3、被裝飾函式識別符號指向返回的函式物件
4、語法糖: @deco
上述閉包的例子,in_dec 被 dec 所裝飾,那可以說裝飾器其實就是閉包的一個本質使用
為了演示 裝飾器 的功能,以上述程式碼為例,進行 裝飾器 寫法
# -*- coding:utf-8 -*- def dec(func): print "call dec" def in_dec(*args): print "call in_dec" if len(args) == 0: print "傳入引數列表為0" return 0 for val in args: if not isinstance(val,int): print "傳入非整數引數" return 0 return func(*args) return in_dec @dec ###求成績總分 def my_sum(*args): return sum(args) ###求成績平均分 def my_average(*args): return sum(args)/len(args)
所以沒有顯示呼叫,但是執行以下:
>>> ================================ RESTART ================================ >>> call dec >>>
結果證明 呼叫 了 dec 函式,那應該是 裝飾器語法糖 @dec 時進行了呼叫,但是它返回了一個函式,那被誰接受了呢? 是被他裝飾的 my_sum 接受了。
為了證明,來呼叫下 my_sum
print my_sum(1,2,3,4,5) print my_sum(1,2,3,4,5,`6`)
結果:
>>> ================================ RESTART ================================ >>> call dec ####### 語法糖呼叫一次裝飾函式 dec call in_dec 15 call in_dec 傳入非整數引數 0 >>>
證明是對的;
@dec 相當於上述的
my_sum = dec(my_sum)
也就可以認為 被重新賦值的 my_sum 相當於裝飾器 dec 的內部函式 in_dec
裝飾器 dec 的引數 func 相當於 被修飾的函式 my_sum
裝飾器 例項二、
# -*- coding:utf-8 -*- def dec(func): print "call dec" def in_dec(): print "call in_dec" func() @dec def test_dec(): print "call test_dec" print type(test_dec) test_dec()
執行結果:
>>> ================================ RESTART ================================ >>> call dec <type `NoneType`> Traceback (most recent call last): File "D:xisuoxxq.py", line 15, in <module> test_dec() TypeError: `NoneType` object is not callable >>>
可以看到這裡的 test_dec 是 NoneType型別,無法呼叫,那是因為我們在裝飾器函式沒有進行顯示的 return ,python 預設返回 None,
所以裝飾器函式必須顯示的進行 return in_func (這裡為 return in_dec)
修改後 執行
# -*- coding:utf-8 -*- def dec(func): print "call dec" def in_dec(): print "call in_dec" func() return in_dec #結論:必須返回 @dec def test_dec(): print "call test_dec" print type(test_dec) test_dec()
>>> ================================ RESTART ================================ >>> call dec <type `function`> call in_dec call test_dec >>>
上面提到了(紅字)函式間的關係(相當於物件引用關係),那賦值的被修飾函式的 引數 就必須三個函式都對應起來
# -*- coding:utf-8 -*- def dec(func): print "call dec" def in_dec(x,y): ##引數要對應起來 print "call in_dec" func(x,y) ##引數要對應起來 return in_dec @dec def test_dec(x,y): ##引數要對應起來 print "call test_dec ",x + y print type(test_dec) test_dec(3,5)
執行結果:
>>> ================================ RESTART ================================ >>> call dec <type `function`> call in_dec call test_dec 8 >>>