DAY 9. 閉包和裝飾器
9.1 閉包
閉包就是內部函數對外部函數作用域內變量的引用
可以看出
- 閉包是針對函數的,還有兩個函數,內部函數和外部函數
- 閉包是為了讓內部函數引用外部函數作用域內的變量的
我們先寫兩個函數
def fun1():print("我是fun1")def fun2():print("我是fun2")
這樣fun2就作為fun1的內部函數,此時在函數外部是無法調用Fun2的,因為
- fun2實際上相當于fun1的一個屬性(方法),作用域是fun1的塊作用域,全局作用域中無法找到,
- 函數內屬性的生命周期是在函數運行期間,在fun1中只是定義了fun2,并沒有調用它
為了讓fun2跳出fun1的生命周期,我們需要返回fun2,這樣在外部獲取到的fun1的返回值就是fun2,這樣調用fun1的返回值就是調用了fun2,如:
def fun1():print("我是fun1")def fun2():print("我是fun2")return fun2var = fun1()
var()
# 我是fun1
# 我是fun2
當然,這還不是一個閉包,閉包是引用了自由變量的函數,所謂自由變量可以理解為局部變量,如果fun2調用了fun1中的變量,那么fun2就是一個閉包了。如
def fun1(var1):def fun2():print(f"var1 = {var1}")return fun2var = fun1(1)
var() # var1 = 1
閉包的作用
閉包私有化了變量,實現了數據的封裝,類似于面向對象
def fun1(obj):def fun2():obj[0] += 1print(obj)return fun2if __name__ == '__main__':mylist = [i for i in range(5)]var = fun1(mylist)var()var()var()# [1, 1, 2, 3, 4]# [2, 1, 2, 3, 4]# [3, 1, 2, 3, 4]
9.2 裝飾器
閉包在python中有一個重要的用途就是裝飾器,裝飾器接受被裝飾的函數作為參數并執行一次調用,裝飾器的本質還是一個閉包
def func1(func):def func2():print("func2")return func()return func2@func1
def Demo():print("Demo")if __name__ == '__main__':Demo()# func2# Demo
- 首先,
@func1
是一顆語法糖,等價于func1(Demo)()
- 外部函數必須能接收一個參數,也只能接受一個參數,如果有多個參數,必須再套一個函數,因為在使用
@
語法糖時,會自動把被修飾函數作為參數傳遞給裝飾器 - 內部函數必須返回被裝飾函數的調用
運行流程:
- 把被修飾函數作為參數傳遞給裝飾器,這時函數返回的是閉包函數func2
- 隱式地調用func2,相當于
func2()
,執行函數體,輸出func2,這時函數返回值是func()
,返回的直接是被修飾函數的調用,相當于直接執行被修飾函數,輸出Demo
相當于:
def func1(func):def func2():print("func2")return func()return func2# @func1
def Demo():print("Demo")if __name__ == '__main__':# s = Demo()# 先把被修飾函數作為參數傳遞給修飾器,這里的s就是func2s = func1(Demo)# 調用閉包函數s()print(s)# func2# Demo# <function func1.<locals>.func2 at 0x00000117F163AD90>
9.2.1 裝飾器帶參數
def func1(num):def func2(func):def func3():if num >10:print("大于10")else:print("小于10")return func()return func3return func2@func1(num=12)
def Demo():print("Demo")if __name__ == '__main__':Demo()b
執行流程
- 將裝飾器的參數傳遞給第一層函數,并返回第二層函數func2
- 將被修飾函數作為參數傳遞給第二層函數func2,隱式調用func2,返回閉包函數
- 執行閉包函數,并返回被修飾函數的調用(執行被修飾函數)
9.2.2 被修飾函數帶參數
如果被修飾函數帶有參數,需要把參數傳遞給內層閉包函數,返回被修飾函數的調用時記得加參數
def func1(func):def func2(arg):arg += 1# 記得加參數return func(arg)return func2@func1
def Demo(arg):print(arg)if __name__ == '__main__':Demo(11) # 12
9.2.3 例
- 求斐波那契數列任意一項的值
import timedef code_time(func):'''修飾器,用來打印函數運行時間:param func: 被修飾函數:return: func'''start_time = time.time()def closer(*args,**kwargs):result = func(*args,**kwargs)codeTime = time.time() - start_timeprint(f"This code runs at:{codeTime}")return resultreturn closerdef _Fibonacci(n):if n <= 1:return 1else:return _Fibonacci(n-1) + _Fibonacci(n-2)@code_time
def Fibonacci(n):return _Fibonacci(n)if __name__ == '__main__':var = Fibonacci(40)print(var)# This code runs at:61.738335609436035# 165580141
發現代碼效率非常低,輸出第四十個值需要一分多鐘,這是應為每計算一個值,需要計算前兩個值,這里有很多重復的,如
10||-----------------|9 8
|--------| |--------|
8 7 7 67,8被重復計算多次
所以需要把已經計算過的儲存起來,計算之前先判斷有沒有計算過,沒計算過再計算,修改程序為:
import timedef code_time(func):'''修飾器,用來打印函數運行時間:param func::return:'''start_time = time.time()def closer(*args,**kwargs):result = func(*args,**kwargs)codeTime = time.time() - start_timeprint(f"This code runs at:{codeTime}")return resultreturn closer
resultList = {0:1,1:1}
def _Fibonacci(n):if n <= 1:return 1else:if n-1 in resultList:a = resultList[n-1]else:a = _Fibonacci(n-1)resultList[n-1] = aif n-2 in resultList:b = resultList[n-2]else:b = _Fibonacci(n-2)resultList[n-2] = breturn a + b@code_time
def Fibonacci(n):return _Fibonacci(n)if __name__ == '__main__':var = Fibonacci(40)print(var)# This code runs at:0.0# 165580141
速度快了很多,但重復的代碼是不能忍受的,使用修飾器重新一下:
import timedef code_time(func):start_time = time.time()def closer(*args, **kwargs):result = func(*args, **kwargs)codeTime = time.time() - start_timeprint(f"This code runs at:{codeTime}")return resultreturn closerdef modify(func):catch = {0: 1, 1: 1}def closer(*args):if args not in catch:catch[args] = func(*args)return catch[args]return closer@modify
def _Fibonacci(n):if n <= 1:return 1else:return _Fibonacci(n - 1) + _Fibonacci(n - 2)@code_time
def Fibonacci(n):return _Fibonacci(n)if __name__ == '__main__':var = Fibonacci(40)print(var)
有20節樓梯,一次可以走1,2,3,4級,總共有多少種走法
from my_python_package import code_timedef Modify(c = None):if c == None:c = {}def modify(func):catch = cdef closer(*args):if args[0] not in catch:catch[args[0]] = func(*args)return catch[args[0]]return closerreturn modify@Modify()
def _Stairs(num, steps):count = 0if num == 0:count = 1elif num > 0:for step in steps:count += _Stairs(num-step,steps)return count@code_time
def Stairs(num,steps):count = _Stairs(num,steps)return countif __name__ == '__main__':num = 20steps = [step for step in range(1,5)]count = Stairs(num, steps)print(count)# Stairs runs at: 0.0 s# 283953
9.3 總結
-
閉包:內部函數調用了外部函數作用域內的變量
- 針對函數
- 要有自由變量(私有變量)
- 要點:內部函數要跳出外部函數的生命周期,需要外部函數把他return出來
-
裝飾器:
- 基礎:閉包
- 作用:不修改原來代碼的基礎上拓展原函數功能
- 用處:修改API功能,AOP編程
- 要點:@語法糖,函數執行順序
-
參考鏈接
Python高級編程技巧(進階)(已完結)
Python的閉包與裝飾器