python中閉包函式與裝飾器函式
閉包
首先知道閉包函式的語法特徵:
- 函式巢狀定義
- 外部函式返回內部函式的引用
- 內部函式可以呼叫外部函式的自由變數
外部函式的作用是建立內部函式並且返回內部函式的引用。
def line(k, b):
"""外部函式的作用: 建立內部函式並且返回內部函式的引用"""
def line_in(x):
y = k * x + b
print(y)
return line_in
line1 = line(1,1)
line1(10)
line1(20)
line2 = line(2,2)
line2(10)
line1(10)
line1和line2都是inner函式程式碼的引用 為什麼執行結果不一樣呢? 因為兩個閉包為不同的物件且使用自由變數不一樣,所以閉包運算的結果就不一樣
閉包的內部函式可以修改自由變數的值,在內部函式使用nonlocal 關鍵字
def line(k, b): """外部函式的作用: 建立內部函式並且返回內部函式的引用""" # d = [k] def line_in(x): # py3中 nonlocal關鍵字用以修改 自由變數<不可變型別> nonlocal k k = k + 1 y = k * x + b # py2 py3都支援的方式 - 間接使用 並沒有修改真正k的值 # d[0] += 1 # y = d[0] * x + b print(y) return line_in l1 = line(1,1) # 列印內部函式的自由變數 print(l1.__closure__) print(l1.__closure__[0]) print(l1.__closure__[0].cell_contents) l1(99)
(<cell at 0x000001EEFC387738: int object at 0x000000006E556DE0>, <cell at 0x000001EEFC387C48: int object at 0x000000006E556DE0>) <cell at 0x000001EEFC387738: int object at 0x000000006E556DE0> 1 199
裝飾器
裝飾器是基於閉包函式,遵循封閉開放的程式碼設計原則,在保證不修改原始碼的基礎上擴充套件程式碼功能。
def check(func): #def check(*args, **kwargs): def wrapper(*args, **kwargs): print("新增驗證功能") func(*args, **kwargs) print("新增結束處理功能") return wrapper @check def func1(num): """不容易發生改變的功能""" print("this is fun1") print("fun1 num %s" % num) @check def func2(num1,num2): print("this is func2") print("func2 num1 %s num2 %s" % (num1, num2)) # func1 = check(func1) func1(2) func2(3, 4)
新增驗證功能 this is fun1 fun1 num 2 新增結束處理功能 新增驗證功能 this is func2 func2 num1 3 num2 4 新增結束處理功能
check是基於閉包的裝飾器函式,func是被裝飾函式的引用。
裝飾器函式只能接收一個引數,即被裝飾函式的引用。
裝飾器可以裝飾有任意引數的函式。
使用裝飾器的方式,@裝飾器名 放在被裝飾函式的上一行。
裝飾器的靈魂程式碼即為: func1 = check(func1)
@check 就是對靈魂程式碼的封裝,很方便
裝飾器demo 統計氣泡排序耗時
import time
import random
def resume_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
# 接收func函式的返回值
result = func(*args, **kwargs)
end_time = time.time()
cost_time = end_time - start_time
print("cost_time:%s" % cost_time)
# 返回func函式的返回值
return result
return wrapper
@resume_time
def bubble_sort(list):
time.sleep(0.6)
n = len(list)
for i in range(n-1):
for j in range(n-1-i):
j = i + 1
if list[j] > list[j+1]:
list[j], list[j+1] = list[j+1], list[j]
return list
lyst = [i for i in range(1000)]
random.shuffle(lyst)
print(bubble_sort(lyst))
由於裝飾器函式只能接收一個引數,若想要多傳入幾個引數,使裝飾器功能更加豐富如何做?
可以使用裝飾器工廠,傳入一個flag引數,當flag為0時耗時時間取浮點形式,flag為1時取整型。
import time
# 接收引數生成裝飾器
def factory(flag):
# flag 向下傳遞
def resume_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
cost_time = end_time - start_time
# 接收上層傳來的flag
if flag == 0:
print("cost_time:%s" % cost_time)
elif flag == 1:
print("cost_time:%d" % int(cost_time))
return result
return wrapper
return resume_time
# 呼叫factory函式傳入flag為1
@factory(1)
def bubble_sort(list):
time.sleep(0.6)
n = len(list)
for i in range(n-1):
for j in range(n-1-i):
j = i + 1
if list[j] > list[j+1]:
list[j], list[j+1] = list[j+1], list[j]
return list
lyst = [i for i in range(1000)]
random.shuffle(lyst)
print(bubble_sort(lyst))
下面的裝飾過程 1. 呼叫factory(1) 2. 將步驟1得到的返回值,即resume_time返回, 然後resume_time(bubble_sort) 3. 將resume_time(bubble_sort)的結果返回,即wrapper 4. 讓bubble_sort = wrapper,即bubble_sort現在指向wrapper
類裝飾器
裝飾器函式其實是這樣一個介面約束,它必須接受一個callbale物件作為引數,然後返回一個callable物件。在Pthon中yi一般callable物件都是函式,但還有一種情況就是某個物件重寫了__call__()方法,那麼這個物件就是callable的。
class Test(object):
def __init__(self, func):
print("---初始化---")
print("func name is %s"%func.__name__)
self.__func = func
def __call__(self):
print("---裝飾器中的功能---")
self.__func()
@Test
def test():
print("----test---")
test()
---初始化--- func name is test ---裝飾器中的功能--- ----test---
說明: 1. 當用Test來裝作裝飾器對test函式進行裝飾的時候,首先會建立Test的例項物件 並且會把test這個函式名當做引數傳遞到__init__方法中 即在__init__方法中的屬性__func指向了test指向的函式 2. test指向了用Test創建出來的例項物件 3. 當在使用test()進行呼叫時,就相當於讓這個物件(),因此會呼叫這個物件的__call__方法 4. 為了能夠在__call__方法中呼叫原來test指向的函式體,所以在__init__方法中就需要一個例項屬性來儲存這個函式體的引用 所以才有了self.__func = func這句程式碼,從而在呼叫__call__方法中能夠呼叫到test之前的函式體