天天看點

Python之裝飾器

 什麼是裝飾器?

在說裝飾器之前啊. 我們先說一個軟體設計的原則: 開閉原則, 又被成為開放封閉原則,你的代碼對功能的擴充是開放的你的程式對修改源代碼是封閉的. 這樣的軟體設計思路可以更好的維護和開發。

  開放:對功能擴充開放

  封閉:對修改代碼封閉

談裝飾器前,還要先要明白一件事,Python 中的函數可以像普通變量一樣當做參數傳遞給另外一個函數,例如:

def foo():
    print("foo")

def bar(func):
    func()

bar(foo)      

裝飾器的目的: 在不改變原來代碼的基礎上. 給代碼添加新功能。

 裝飾器形成過程

簡單裝飾器

def func1():
    print("歡迎通路首頁")

# 添加一個登入驗證的功能
def wrapper(fn):
    def inner():
        print("請先登入....")
        fn()  
    return inner

func1 = wrapper(func1)
func1()

# 裝飾器的基本雛形
# def wrapper(fn): # fn:目标函數.
#     def inner():
#         '''執行函數之前'''
#         fn() # 執行被裝飾的函數
#         '''執行函數之後'''
#     return inner      

來看一下,執行流程:

Python之裝飾器

文法糖

   但是如果有多個函數,都需要添加登入驗證的功能,每次都得func1 = timer(func1)?這樣還是有點麻煩,因為這些函數的函數名可能是不相同,有func1,func2,graph,等等,是以更簡單的方法,python給你提供了,那就是文法糖。 @裝飾器

def wrapper(fn):
    def inner():
        print("請先登入....")
        fn()
    return inner

@wrapper  # 相當于 => func1 = wrapper(func1)
def func1():
    print("歡迎通路首頁")

func1()      

有個小問題,就是檢視func1函數名和函數注釋都變成inner了。

print(func1.__doc__)     # 檢視函數注釋資訊
print(func1.__name__)   # 檢視函數名
# 我是内部函數
# inner

如何解決???
from functools import wraps
def wrapper(fn):
    @wraps(fn)   # 改變inner的名字
    def inner():
        """我是内部函數"""
        print("請先登入....")
        fn()
    return inner

@wrapper  # 相當于 => func1 = wrapper(func1)
def func1():
    """ 首頁函數 """
    print("歡迎通路首頁")

func1()

print(func1.__doc__)
print(func1.__name__)
#  首頁函數 
# func1      

被裝飾函數帶參數和傳回值

from functools import wraps
def wrapper(fn):  # fn: 目标函數, 被裝飾的函數
    @wraps(fn)   # 改變inner的名字
    def inner(*args,**kwargs):  # 萬能參數  *args, **kwargs: 接收參數
        """我是内部函數"""
        print("請先登入....")
        ret = fn(*args,**kwargs)      # 調用目标函數.
        """被裝飾函數執行之後"""
        return ret                    # 傳回結果
    return inner

@wrapper  # 相當于 => func1 = wrapper(func1)
def func1(usernamee):
    """ 首頁函數 """
    print("歡迎 %s 通路首頁" % usernamee)
    return True

@wrapper  # 相當于 => func2 = wrapper(func2)
def func2(name,age):
    print("名字:%s 年齡:%s" %(name,age))
    return True

ret = func1("lishichao")   # 實際執行的是inner 參數傳給了inner函數
print(ret)

func2("lishichao","19")      

帶有參數的裝飾器

給裝飾器傳參,可以控制裝飾器是否使用。

def wrapper(flag):  # 裝飾器參數
    def inner1(fn):  # fn: 目标函數, 被裝飾的函數
        def inner2(*args,**kwargs):  # 萬能參數  *args, **kwargs: 接收參數
            """我是内部函數"""
            if flag:  # 判斷傳入的參數  True就執行裝飾器 False不執行裝飾器
                print("請先登入....")
            ret = fn(*args,**kwargs)      # 調用目标函數.
            """"""
            return ret                    # 傳回結果
        return inner2
    return inner1

@wrapper(False) # 執行流程: 先執行wrapper(False)  傳回裝飾器 再和 @ 拼接起來 @inner1
def func1(usernamee):
    """ 首頁函數 """
    print("歡迎 %s 通路首頁" % usernamee)
    return True

ret = func1("lishichao")   # 實際執行的是inner 參數傳給了inner函數
print(ret)      

執行流程:

Python之裝飾器

多個裝飾器裝飾一個函數

def wrapper1(fn):
    def inner(*args, **kwargs):
        print("第一個裝飾器開始")
        ret = fn(*args, **kwargs)
        print("第一個裝飾器結束")
        return ret
    return inner

def wrapper2(fn):
    def inner(*args, **kwargs):
        print("第二個裝飾器開始")
        ret = fn(*args, **kwargs)
        print("第二個裝飾器結束")
        return ret
    return inner

def wrapper3(fn):
    def inner(*args, **kwargs):
        print("第三個裝飾器開始")
        ret = fn(*args, **kwargs)
        print("第三個裝飾器結束")
        return ret
    return inner

@wrapper1
@wrapper2
@wrapper3
def func():
    print("瞬間來三")

func()
# 執行結果
# 第一個裝飾器開始
# 第二個裝飾器開始
# 第三個裝飾器開始
# 瞬間來三
# 第三個裝飾器結束
# 第二個裝飾器結束
# 第一個裝飾器結束      

執行流程:

wrapper1   
wrapper2   
wrapper3   # 調用函數執行之前
func
wrapper3   # 調用函數執行之後
wrapper2
wrapper1      
Python之裝飾器

練習題

1、裝飾器的固定格式

Python之裝飾器
Python之裝飾器
from functools import wraps
def wrapper(flag):  # 接收裝飾器的參數
    def inner1(fn): # 接收的是被裝飾函數的函數名
        @wraps(fn)  # 改變inner2函數的名字為被裝飾函數的名字
        def inner2(*args,**kwargs):   # 無敵傳參,接收參數
            if flag:  # 判斷是否執行裝飾器
                print("被裝飾函數執行之前")
            ret = fn(*args,**kwargs)  #被裝飾函數, 調用參數
            print("被裝飾函數執行之後")
            return ret   # 傳回值
        return inner2
    return inner1

@wrapper(True)
def func1():
    print("我是被裝飾函數")
    return True

func1()
print(func1.__name__)      

裝飾器的固定格式

2、編寫裝飾器,在每次執行被裝飾函數之前列印一句’每次執行被裝飾函數之前都得先經過這裡,這裡根據需求添加代碼’

Python之裝飾器
Python之裝飾器
def wrapper(fn):
    def inner(*args,**kwargs):
        print("每次執行被裝飾函數之前都得先經過這裡,這裡根據需求添加代碼")
        ret = fn(*args,**kwargs)  #調用被裝飾函數,ret接收被裝飾函數的傳回值
        return ret
    return inner

@wrapper
def func():
    print("我是被裝飾函數")
    return "被裝飾函數的傳回值"

ret = func()
print(ret)      

View Code

3、編寫裝飾器,在每次執行被裝飾函數之後列印一句’每次執行完被裝飾函數之後都得先經過這裡,這裡根據需求添加代碼’

Python之裝飾器
Python之裝飾器
def wrapper(fn):
    def inner(*args,**kwargs):
        ret = fn(*args,**kwargs)
        print("每次執行完被裝飾函數之後都得先經過這裡,這裡根據需求添加代碼")
        return ret
    return inner

@wrapper
def func():
    print("我是被裝飾函數")

func()      

4、編寫裝飾器,在每次執行被裝飾函數之前讓使用者輸入使用者名,密碼,給使用者三次機會,登入成功之後,才能通路該函數.

Python之裝飾器
Python之裝飾器
flag = False
def login():
    num = 1
    while num <= 3:
        username = input("請輸入使用者名:").strip().lower()
        password = input("請輸入密碼:").strip().lower()
        if username == "lishichao" and password == "123":
            global flag
            flag = True
            break
        else:
            num += 1
            print("輸入錯誤,請重新輸入")
            continue
    else:
        print("輸入超過三次,已鎖定")


def login_wrapper(fn):
    def inner():
        login()
        if flag:
            fn()
        else:
            print("登入失敗")
    return inner

@login_wrapper
def func1():
    print("歡迎通路文章頁面")      

5、編寫裝飾器,為多個函數加上認證的功能(使用者的賬号密碼來源于檔案,隻支援單使用者的賬号密碼,給使用者三次機會)要求登入成功一次,後續的函數都無需再輸入使用者名和密碼

Python之裝飾器
Python之裝飾器
import sys
flag = False

def sign_up():
    with open("user_info",mode="w",encoding="utf-8") as f:
        username = input("請輸入新增賬號:").strip()
        password = input("請輸入注冊密碼:").strip()
        f.write("%s|%s\n" %(username,password))
        print("注冊成功,請登入")

def login():
    num = 1
    while num <= 3:
        username = input("請輸入賬号:").strip()
        if username == "":
            print("賬号不能為空")
            continue
        password = input("請輸入密碼:").strip("|")
        if password == "":
            print("密碼不能為空,請重新輸入")
            continue
        num += 1
        with open("user_info", mode="r", encoding="utf-8") as f:
            for i in f:
                user_list = i.strip().split("|")
                if username == user_list[0] and password == user_list[1]:
                    print("登入成功")
                    global flag
                    flag = True
                    return
            else:
                print("登入失敗,賬号或密碼不正确.")
    else:
        print("===========================================")
        print("登入超過三次,賬号已鎖定")
        print("===========================================")
        sys.exit()


def wrapper(fn):
    def inner(*args,**kwargs):
        if flag:
            ret = fn(*args,**kwargs)
            return ret
        else:
            login()
    return inner

@wrapper
def func1():
    print("歡迎通路文章頁面")

@wrapper
def func2():
    print("歡迎通路管理頁面")

@wrapper
def func3():
    print("歡迎通路評論頁面")

dic = {
    "1":sign_up,
    "2":login,
    "3":func1,
    "4":func2,
    "5":func3
}

def main():
    while 1:
        print("""
           請選擇:
                1.注冊
                2.登入
                3.通路文章頁面
                4.通路管理頁面
                5.通路評論頁面
                [q] 退出
        """)
        n = input(">>>").strip().lower()
        if n == "q":
            break
        else:
            dic[n]()

main()      

6、編寫裝飾器,為多個函數加上認證的功能(使用者的賬号密碼來源于檔案,可支援多賬号密碼)要求登入成功一次(給三次機會),後續的函數都無需再輸入使用者名和密碼。

Python之裝飾器
Python之裝飾器
import sys
flag = False

def sign_up():
    with open("user_info",mode="a",encoding="utf-8") as f:
        username = input("請輸入新增賬號:").strip()
        password = input("請輸入注冊密碼:").strip()
        f.write("%s|%s\n" %(username,password))
        print("注冊成功,請登入")

def login():
    num = 1
    while num <= 3:
        username = input("請輸入賬号:").strip()
        if username == "":
            print("賬号不能為空")
            continue
        password = input("請輸入密碼:").strip("|")
        if password == "":
            print("密碼不能為空,請重新輸入")
            continue
        num += 1
        with open("user_info", mode="r", encoding="utf-8") as f:
            for i in f:
                user_list = i.strip().split("|")
                if username == user_list[0] and password == user_list[1]:
                    print("登入成功")
                    global flag
                    flag = True
                    return
            else:
                print("登入失敗,賬号或密碼不正确.")
    else:
        print("===========================================")
        print("登入超過三次,賬号已鎖定")
        print("===========================================")
        sys.exit()


def wrapper(fn):
    def inner(*args,**kwargs):
        if flag:
            ret = fn(*args,**kwargs)
            return ret
        else:
            login()
    return inner

@wrapper
def func1():
    print("歡迎通路文章頁面")

@wrapper
def func2():
    print("歡迎通路管理頁面")

@wrapper
def func3():
    print("歡迎通路評論頁面")

dic = {
    "1":sign_up,
    "2":login,
    "3":func1,
    "4":func2,
    "5":func3
}

def main():
    while 1:
        print("""
           請選擇:
                1.注冊
                2.登入
                3.通路文章頁面
                4.通路管理頁面
                5.通路評論頁面
                [q] 退出
        """)
        n = input(">>>").strip().lower()
        if n == "q":
            break
        else:
            dic[n]()

main()      

7、給每個函數寫一個記錄日志的功能

功能要求:每一次調用函數之前,要将函數名稱,時間節點記錄到log的日志中。

所需子產品:

import time

struct_time = time.localtime()

print(time.strftime("%Y‐%m‐%d %H:%M:%S",struct_time))

Python之裝飾器
Python之裝飾器
def wrapper_log(flag):
    def wrapper(fn):
        def innrt(*args,**kwargs):
            if flag:
                import time
                struct_time = time.localtime()
                Time = time.strftime("%Y-%m-%d %H:%M:%S",struct_time)
                func_name = fn.__name__
                with open("func_accecc.log",mode="a",encoding="utf-8") as f:
                    f.write("在 %s    執行函數: %s\n" %(Time,func_name))
            ret = fn(*args,**kwargs)
            return ret
        return innrt
    return wrapper

@wrapper_log(True)
def func1():
    print("歡迎通路文章頁面")

@wrapper_log(True)
def func2():
    print("歡迎通路評論頁面")

@wrapper_log(True)
def func3():
    print("歡迎通路管理頁面")

func1()
func2()
func3()