裝飾器、語法糖、修復技術

随机昵称yi發表於2024-05-20

裝飾器

【一】什麼是裝飾器

  • 裝飾器就是為被裝飾的物件新增新的功能
  • 器 ---> 代表工具 增加後呼叫方式不變
  • 在不改變原函式程式碼和呼叫方式的基礎上增加額外的新功能

【二】裝飾器的用途

  • 對上線後的程式進行新功能的增加和修改
  • 給一個功能增加新的需求或者改變原來的程式的執行邏輯

【三】裝飾器的分類

【1】有參裝飾器

【2】無參裝飾器

【四】裝飾的原理

閉包函式 + 函式物件的組合使用

無參裝飾器

【一】什麼是無參裝飾器

沒有引數的裝飾器

【二】推導裝飾器的語法

為一段程式碼增加計時功能

麵條版

import time
start_time = time.time()

print("開始")

time.sleep(2)

print("結束")

end_time = time.time()

print(end_time - start_time)


# 開始
# 結束
# 2.003873825073242
函式版
import time
def time_():
    print("開始")
    time.sleep(2)
    print("結束")

start_time = time.time()
time_()
end_time = time.time()

print(end_time - start_time)

# 開始
# 結束
# 2.015000104904175
裝飾器版
import time

def time_():
    start_time = time.time()
    print("開始")

    time.sleep(2)

    end_time = time.time()
    print("結束")
    
    return print(end_time - start_time)

def outer(func):
    def inner():
        func()
        
    return inner

abc = outer(time_)
abc()

# 開始
# 結束
# 2.0015313625335693

【三】推導裝飾器

【1】無參裝飾器模板

# 無參裝飾器的模板
def outer(func):
    ''' 
    :param func: 指每一次需要呼叫傳遞的函式
    :return:返回值是內嵌函式 inner 的函式記憶體地址 
    '''
    def inner():
        # 可以加邏輯校驗
        # 當符合指定條件後,可以允許執行傳遞進來的函式
        # 不符合條件的時候不執行函式
        func()
        # 做邏輯校驗,func 函式執行後的邏輯

    return inner

abc = outer(abc)
abc()
  • outer(abc)指的是函式outer 在引數為abc時的返回值inner

  • abc()則是內嵌函式inner()

【2】登入校驗

user_data_dict = {'username': "qwer", "password": "123"}
def outer(func):
    def inner():
        username = input("username :>>>> ")
        password = input("password :>>>> ")
        # 否則列印失敗
        if username == user_data_dict.get('username') and password == user_data_dict.get('password'):
            print(f"登入成功")
            func()
        else:
            print("登入失敗")

    return inner


def transform():
    print(f"這是在轉賬功能")
transform = outer(transform)
transform()

# transform = outer(transform)  # transform  = inner = outer(transform)
# transform()  # transform() = inner() = outer(transform)()

def withdral():
    print(f"這是取款功能")
withdral = outer(withdral)
withdral()

有參裝飾器

【1】無參裝飾器

無參裝飾器無法傳遞引數

def outer(func):
    def inner():
        func()
    return inner

def transform(username,money):
    print(f"向{username}轉賬{money}")

transform = outer(transform)
transform()# 報錯

【2】有參裝飾器

根據指定的位置引數傳遞引數

def outer(func):
    def inner(username,money):
        func(username,money)
    return inner

def transform(username,money):
    print(f"向{username}轉賬{money}")

transform = outer(transform)
transform(1,2)

【3】最佳化

用可變長位置引數和可變長關鍵字引數接收到函式所需要的所有引數

def outer(func):
    def inner(*args,**kwargs):
        func(*args,**kwargs)
    return inner

def transform(username,money):
    print(f"向{username}轉賬{money}")

transform = outer(transform)
transform(1,2)

【4】有參裝飾器模板

def outer(func):
    def inner(*args, **kwargs):
        func(*args, **kwargs)
    return inner

裝飾器語法糖

【1】無參裝飾器語法糖

def timer(func):
    def inner():
        start = time.time()
        func()
        end = time.time()
        print(f"總耗時 :>>>> {end - start} s")

    return inner

@timer  # 等效於 transform = timer(transform)
def transform():
    time.sleep(1)

# transform = timer(transform)
# transform()

transform()

【2】有參裝飾器語法糖

(1)單語法糖

login_dict = {
    'username':'qwer'
}

def login(func):
    def inner(*args,**kwargs):
        if login_dict.get('username'):
            func(*args,**kwargs)
        else:
            print("使用者不存在")
    return inner
@login  # 等效於 transform = timer(transform)
def transform(username,abc):
    print({username},{abc})
# transform = timer(transform)
# transform()

transform(1,2)

(2)多語法糖

login_dict = {'username': None}


def decrator(tag):
    '''

    :param tag: 'login_auth' ---> 登入認證裝飾器
    'timer' ---> 返回 計時裝飾器
    :return:
    '''
    if tag == "login_auth":
        def login_auth(func):
            def inner(*args, **kwargs):
                if login_dict.get('username'):
                    func(*args, **kwargs)
                else:
                    print(f"請先登入!")
            return inner
        return login_auth
    elif tag == "timer":
        def timer(func):
            def inner(*args, **kwargs):
                start = time.time()
                func(*args, **kwargs)
                end = time.time()
                print(f"總耗時 :>>>> {end - start} s")

            return inner

        return timer


def transform(username, money):
    print(f"{username} | {money}")
    time.sleep(1)

# transform = decrator(tag="login_auth")(transform)
# transform('qwer',123)
# print(transform)

transform = decrator(tag="timer")(transform)
transform('qwer',123)
print(transform)

(3)最佳化多語法糖

login_dict = {'username': None}


def decrator(tag):
    '''

    :param tag: 'login_auth' ---> 登入認證裝飾器
    'timer' ---> 返回 計時裝飾器
    :return:
    '''
    if tag == "login_auth":
        def login_auth(func):
            def inner(*args, **kwargs):
                if login_dict.get('username'):
                    func(*args, **kwargs)
                else:
                    print(f"請先登入!")
            return inner
        return login_auth
    elif tag == "timer":
        def timer(func):
            def inner(*args, **kwargs):
                start = time.time()
                func(*args, **kwargs)
                end = time.time()
                print(f"總耗時 :>>>> {end - start} s")

            return inner

        return timer
    else:
        print(f"無引數的",tag)

# @decrator(tag='login_auth')
@decrator(tag='timer')
def transform(username, money):
    print(f"{username} | {money}")
    time.sleep(1)

transform("dream",999)

【3】多層語法糖案例

def a(func):
    # 【二】呼叫 a 函式觸發
    print(1111)
    def inner(*args,**kwargs):
        # 【三】執行 c()
        print(2222)
        func(*args,**kwargs) # 執行 b_inner(c)
         # 【七】結束上面的 b_inner 回來
        print(3333)
    return inner

def b(func):
    # 【一】進來 b 函式
    print(4444)
    def inner(*args,**kwargs):
         # 【四】因為在 a 的 inner 裡面呼叫了 func --> 就是我自己
        print(5555)
        func(*args,**kwargs)
        # 【六】執行完 c 又回來繼續走
        print(6666)
    return inner

@a
@b 
# 多層語法糖的時候誰在下先呼叫誰,但是執行校驗的時候是誰在上先校驗誰
def c():
    # 【五】回到真正的 c
    print(7777)

# @a 
# @b 
相當於
# c = b(c)
# c = a(c)
# c()


    
執行順序
4444
1111
2222
5555
7777
6666
3333

'''
# 定義順序是一個個挨著定義
# 呼叫順序是倒著誰在下先呼叫誰
# c = b(c)
# c = a(c)
# c()
'''

裝飾器的修復技術

【一】無修復技術

可以透過help函式來檢視函式內部的註釋

import time
# 我們可以透過help函式檢視函式內部的註釋
def timer(func):
    '''
    :param func: 傳入的引數
    :return:
    '''
    def inner(*args, **kwargs):
        '''
        :param args: 可變長位置引數
        :param kwargs: 可變長關鍵字引數
        :return:
        '''
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(end - start)

    return inner


@timer
def run():
    print(f"run 開始")
    time.sleep(2)
    print(f"run 結束")


print(help(timer))
print(help(run))

#檢視函式內部註釋 
timer(func)
    :param func: 傳入的引數
    :return:

None
Help on function inner in module __main__:

inner(*args, **kwargs)
    :param args: 可變長位置引數
    :param kwargs: 可變長關鍵字引數
    :return:

None

【二】問題

  • 裝飾器其實就是在不改變原始碼呼叫方式的基礎上額外增加新功能
  • 別人可以透過help檢視內部的註釋

【三】有修復技術

從 functools 匯入wraps

from functools import wraps

def timer(func):
    '''

    :param func: 傳入的引數
    :return:
    '''
    @wraps(func)
    def inner(*args, **kwargs):
        '''
        :param args: 可變長位置引數
        :param kwargs: 可變長關鍵字引數
        :return:
        '''
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(end - start)

    return inner


@timer
def run():
    print(f"run 開始")
    time.sleep(2)
    print(f"run 結束")
print('外部函式註釋',help(timer))
print('內嵌函式註釋',help(run))

# 檢視函式內部註釋 外部能檢視到註釋,被包住的內嵌看不到註釋
Help on function timer in module __main__:

timer(func)
    :param func: 傳入的引數
    :return:

外部函式註釋 None
Help on function run in module __main__:

run()

內嵌函式註釋 None

相關文章