1. 程式人生 > 實用技巧 >python 閉包和裝飾器

python 閉包和裝飾器

一、閉包

定義:內層函式對外層函式非全域性變數的引用,就會形成閉包,閉包只存在於巢狀函式中

  • 被引用的非全域性變數也稱為自由變數,這個自由變數會與內層函式產生一個繫結關係
  • 自由變數不會再記憶體中消失
  • 閉包的作用,保證資料的安全 (返回的函式物件,不僅僅是一個函式物件,在該函式外還包裹了一層作用域,這使得,該函式無論在何處呼叫,優先使用自己外層包裹的作用域)
# 預設情況下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()