一、什么是閉包
先看一個例子:
#定義一個函數
def test(number): #在函數內部在定義一個函數,並且這個函數用到外圍函數的變量
#那么將這個函數及用到的一些變量稱之為閉包
def test_in(number_in): print("在test_in函數內部,number_in的值為:%d"%number_in) return number+number_in #其實這里返回的是閉包,也就是內部的函數引用
return test_in #給test函數賦值,這個20就是參數number
ret = test(20) #注意這里的100就是參數number_in
print(ret(100))
運行結果為:
在test_in函數內部,number_in的值為:100
120
說明:
- 在函數內部在定義一個函數,並且這個函數用到外圍函數的變量,那么將這個函數及用到的一些變量稱之為閉包
- 在其他語言里面不允許函數內部在定義函數,但是python中的閉包可以
二、什么是裝飾器
裝飾器是程序開發中經常會⽤到的⼀個功能,所以這也是Python⾯試中必問的問題。
定義:
- 裝飾器本身就是一個函數
- 為其他函數提供附加功能
- 不改變被修飾函數的源代碼
- 不改變原調用方式
- 裝飾器=高階函數+嵌套函數
知識點:
- 函數本身就是一個變量(意味着可以被復制給一個變量:test=test(1) )
- 高階函數:把函數名當成一個實參傳遞給另一個函數func(test1) (不改變源代碼的前提下添加代碼)
- 返回值中包含函數名return deco (不改變函數的調用方式)
- 嵌套函數:函數中加入新的函數
典型結構:
def func(args): def func_in(args_in): pass
return func_in
三、裝飾器案例
1、先看一個例子
某公司有多個研發部⻔,1個基礎平台部⻔,基礎平台負責提供底層的功能,如:數據庫操作、redis調⽤、監控API等功能。研發部⻔使⽤基礎功能時,只需調⽤基礎平台提供的功能即可。如下:
--------------基礎平台提供的功能--------------
def func1(): pass
def func2(): pass
def func3(): pass
--------------研發部門A使用基礎平台-------------- func1() func2() func3() --------------研發部門B使用基礎平台-------------- func1() func2() func3()
隨着項目進度的深入,產品經理提出,要在基礎平台的提供的所有功能中,添加驗證功能,不能誰都可以使用基礎平台的全部功能,即執行功能前,先進行驗證。
項目經理將此功能交給了小A去實現。
小A就去和每個研發部溝通,讓每個研發部自己把驗證的代碼加上,結果第二天就被辭職了。
項目經理又將此功能交給了小B去實現。
小B吸取小A的經驗,開始自己改代碼:
--------------基礎平台提供的功能--------------
def func1(): #驗證1
#驗證2
pass
def func2(): #驗證1
#驗證2
pass
def func3(): #驗證1
#驗證2
pass
--------------研發部門A使用基礎平台-------------- func1() func2() func3() --------------研發部門B使用基礎平台-------------- func1() func2() func3()
沒過多久小B也被開除了。。。
項目經理又把工作交給了小C,小C對基礎平台代碼進行重構,其他業務部門無需做任何修改
--------------基礎平台提供的功能--------------
def check_login(): #驗證1
#驗證2
pass
def func1(): check_login() pass
def func2(): check_login() pass
def func3(): check_login() pass
--------------研發部門A使用基礎平台-------------- func1() func2() func3() --------------研發部門B使用基礎平台-------------- func1() func2() func3()
項目經理看后表示還不錯,但是感覺還是差了一點點,於是決定不再低調,再也不讓小弟做了,於是自己做了一個方案:
--------------基礎平台提供的功能--------------
def check_login(func): def inner(): #驗證1
#驗證2
func() return inner @check_login def func1(): pass @check_login def func2(): pass @check_login def func3(): pass
--------------研發部門A使用基礎平台-------------- func1() func2() func3() --------------研發部門B使用基礎平台-------------- func1() func2() func3()
對於上述代碼,也是僅僅對基礎平台的代碼進⾏修改,就可以實現在其他⼈調⽤函數 func1(), func2(), func3()之前都進⾏【驗證】操作,並且其他研發部⻔⽆需做任何操作。
單獨以func1()為例講解:
def check_login(func): def inner(): #驗證1
#驗證2
func() return inner @check_login def func1(): pass
python解釋器就會從上到下解釋代碼,步驟如下:
1 def check_login(func): ==>將check_login函數加載到內存 2 @check_login
沒錯, 從表⾯上看解釋器僅僅會解釋這兩句代碼,因為函數在沒有被調⽤之前其內部代碼不會被執⾏。從表⾯上看解釋器着實會執⾏這兩句,但是 @check_login這⼀句代碼⾥卻有⼤⽂章, @函數名 是python的⼀種語法糖
上例@check_login內部會執⾏⼀下操作:
執行check_login函數,並將@check_login下面的函數作為check_login函數的參數,
即@check_login等價於check_login(func1),所以內部就會去執行:
def check_login(func): def inner(): #驗證1
#驗證2
func() #func是參數。此時的func就是函數func1()
#返回inner,inner的內部就是執行func1()函數,但是執行func1()函數前,進行了驗證1,驗證2
return inner
check_login() 的返回值
將執行完的chenk_login函數返回值賦值 給@check_login下面的函數的函數名func1 即將check_login()的返回值再重新賦值給func1,即:
新func1 = def inner(): #驗證1
#驗證2
func() #func是參數。此時的func就是函數func1()
#返回inner,inner的內部就是執行func1()函數,但是執行func1()函數前,進行了驗證1,驗證2
return inner
所以,以后研發部門想要執行func1函數時,就會執行新func1函數,在新func1函數內部先執行驗證,再執行原來的func1函數,然后將原來func1函數的返回值返回給了業務調用者。
四、裝飾器應用
#定義一個裝飾器:實現加粗效果
def makeBold(fn): def wrapped(): return "<b>"+fn()+"</b>"
return wrapped #定義一個裝飾器:實現斜體效果
def makeItalic(fn): def wrapped(): return "<i>"+fn()+"</i>"
return wrapped #使用裝飾器裝飾函數
@makeBold def test(): return "Hello World"
#使用裝飾器裝飾函數
@makeItalic def test1(): return "Hello World" @makeBold @makeItalic def test2(): return "Hello World"
print(test()) print(test1()) print(test2())
運行結果為:
<b>Hello World</b>
<i>Hello World</i>
<b><i>Hello World</i></b>
五. 裝飾器示例
例1:⽆參數的函數
def test_out(func): def test_in(): print("name-%s"%func.__name__) func() return test_in @test_out def test(): pass test()
運行結果為:name-test
例2:被裝飾的函數有參數
def test_out(func): def test_in(a,b): print(a,b) func(a,b) return test_in @test_out def test(a,b): print("a+b=",a+b) test(1,2)
運行結果為:
1 2 a+b= 3
例3:被裝飾的函數有不定⻓參數
def test_out(func): def test_in(*args,**kwargs): func(*args,**kwargs) return test_in @test_out def test(*args,**kwargs): print(args,kwargs) test(1) test(1,2) test(1,2,3,k="v")
運行結果為:
(1,) {} (1, 2) {} (1, 2, 3) {'k': 'v'}
說明:如果被修飾的函數有參數,則裝飾器內部的函數也要有同樣個數的參數才可以匹配成功。
例4:裝飾器中的return
def test_out(func): def test_in(): func() return test_in @test_out def test(): return "hello" result = test() print(result)
運行結果為:None
如果修改裝飾器為 return func() ,則運⾏結果:
def test_out(func): def test_in(): return func() return test_in @test_out def test(): return "hello" result = test() print(result)
運行結果為:hello
⼀般情況下為了讓裝飾器更通⽤,可以有return
例5:裝飾器帶參數,在原有裝飾器的基礎上,設置外部變量

六、類裝飾器
裝飾器函數其實是⼀個接⼝約束,它必須接受⼀個callable對象作為參數,然后返回⼀個callable對象。在Python中⼀般callable對象都是函數,但也有例外。只要某個對象重寫了 __call__() ⽅法,那么這個對象就是callable
class Test(): def __call__(self): print("call me") t = Test() t()
執行結果:call me
類裝飾器demo
class Test(): def __init__(self,func): print("----初始化----") print("func name is %s"%func.__name__) self.__func = func def __call__(self): print("----類裝飾器的功能----") self.__func() print("----類裝飾器執行完畢----") @Test def test(): print("----test----") test()
運行結果為:
----初始化---- func name is test ----類裝飾器的功能----
----test----
----類裝飾器執行完畢----
說明:
- 當用Test來裝作裝飾器對test函數進行裝飾的時候,首先會創建Test的實例對象
- 並且會把test這個函數名當做參數傳遞到__init__方法中,即在__init__方法中的func變量執行了test函數體
- test函數相當於指向了用Test創建出來的實例對象
- 擋在使用test()進行調用時,就相當於讓這個對象(),因此會調用這個對象的__call__方法
- 為了能夠在__call__方法中調用原來的test指向的函數體,所以在__init__方法中就需要一個實例屬性保存這個引用,所以才有了self.__func = func這句代碼,從而在調用__call__方法中就能調用test
