python 閉包和裝飾器
阿新 • • 發佈:2020-07-29
一、閉包
定義:內層函式對外層函式非全域性變數的引用,就會形成閉包,閉包只存在於巢狀函式中
- 被引用的非全域性變數也稱為自由變數,這個自由變數會與內層函式產生一個繫結關係
- 自由變數不會再記憶體中消失
- 閉包的作用,保證資料的安全 (返回的函式物件,不僅僅是一個函式物件,在該函式外還包裹了一層作用域,這使得,該函式無論在何處呼叫,優先使用自己外層包裹的作用域)
# 預設情況下inner函式是沒有辦法被外層執行的 def outer(): name = "小白,自學程式設計" def inner(): print("inner",name) return inner #返回inner的記憶體地址 func = outer() #相當於把inner賦值給func func() #相當於執行了inner print(outer.__code__.co_freevars) # 檢視有沒有自由變數,如果有說明是閉包
二、裝飾器
定義:在不改變原始碼和呼叫方式的情況下增加新功能,裝飾器的本質就是閉包
準守開放封閉原則
封閉:已經實現的模組程式碼不應該被修改
開放:對現有功能的擴充套件開放
2.1、初識別裝飾器
寫一個函式,測試另外同事寫的函式執行效率
版本一
import time def index(): time.sleep(2) print('歡迎訪問部落格園主頁') return 666 def timer(func): def inner(): start_time = time.time() func() stop_time = time.time() print(f'此函式的執行效率為{stop_time-start_time}') return inner #返回inner index = timer(index) index() #相當於執行inner
通過以上的程式碼已經實現了測試執行效率的功能,但是有兩個問題,
- 第一,當執行index() 的時候其實是執行了inner,而inner函式裡邊的func其實才是真正執行的index(),所以原來index裡邊的return 666 無法返回,如果想要返回應該把func()的執行結果賦值給一個變數,然後inner函式return這個變數
- 第二,如果執行index() 需要帶有變數,那麼這個時候會報錯,原因是執行index()相當於執行了 inner(),而執行inner()又相當於執行func(),所以如果想要index()帶有變數,inner()和func()都應該加上相應的變數,可用使用萬能變數,接收任何引數
版本二:
import time
def index(name):
time.sleep(2)
print(f'{name}歡迎訪問部落格園主頁')
return 666
def timer(func):
def inner(*args,**kwargs):
start_time = time.time()
s = func(*args,**kwargs) # 將func的返回結果賦值給一個變數
stop_time = time.time()
print(f'此函式的執行效率為{stop_time-start_time}')
return s # 返回func的返回結果,相當於返回index的返回結果
return inner
index = timer(index)
index('老王')
通過版本二的改進,上邊說的兩個問題已經解決了,但是還有一個問題,和一個改進的地方
- 第一,每次函式在新增裝飾器的時候需要在呼叫函式之前加上一句index = timer(index) ,顯得比較麻煩,python引入了一個語法糖的東西,改進這一缺點
- 第二,在使用裝飾器之後,原函式的屬性會變成包裝器的屬性,如果還想保留原函式的屬性就需要用
functools.wraps
functools.wraps
就是裝飾包裝器的裝飾器
版本三
from functools import wraps #呼叫裝飾包裝器模組
import time
def timer(func):
@wraps(func) # 裝飾包裝器
def inner(*args,**kwargs): #包裝器
start_time = time.time()
s = func(*args,**kwargs)
stop_time = time.time()
print(f'此函式的執行效率為{stop_time-start_time}')
return s
return inner
@timer # 語法糖 相當於index = timer(index)
def index(name):
time.sleep(2)
print(f'{name}歡迎訪問部落格園主頁')
return 666
index('老王')
2.2 、裝飾器模板
def wrapper(f):
def inner(*args,**kwargs):
'''新增額外功能,執行被裝飾函式之前的操作'''
ret = f(*args,**kwargs)
'''新增額外功能,執行被裝飾函式之後的操作'''
return ret
return inner
三、裝飾器的應用
網頁登入驗證
某網站需要給所有頁面新增使用者名稱密碼驗證功能,只有通過驗證的使用者才可以訪問網頁資源,並且在一個網頁通過驗證後,其他網頁可直接訪問,如果使用者密碼輸入錯誤超過三次直接退出程式
from functools import wraps
account={
"is_authenticated":False,#使用者登入了就把這個改成Ture
"username": "laowang", #假裝這裡是資料庫的使用者
"password": "abc123"
}
def login(funk):
@wraps(funk)
def inner(*args):
if account["is_authenticated"] is False:
count = 0
while count < 3:
username = input("user:")
password = input("password:")
if username == account["username"] and password == account["password"]:
print("welcome login.....")
account["is_authenticated"]=True
funk(*args)
break
else:
print("wrong username or password")
count += 1
if count == 3:
exit()
else:
print("使用者以登入,驗證通過....")
funk(*args)
return inner #返回inner的記憶體地址
@login
def home():
print("歡迎來到首頁")
@login
def america():
print("歡迎來到歐美專區")
@login #新增裝飾器,等於henan = login(henan)
def henan(vip_live):
if vip_live > 3: #vip等及大於3的解鎖本專區高階私密
print("歡迎來到河南專區,解鎖本專區高階私密")
else:
print("歡迎來到河南專區")
# henan = login(henan)
home()
henan(4)
america()