Python裝飾器(你想知道的這裡都有)

2020-09-27 09:00:06

1. 裝飾器的定義

就是給已有函數增加額外功能的函數,它本質上就是一個閉包函數

裝飾器的功能特點:

  1. 不修改已有函數的原始碼
  2. 不修改已有函數的呼叫方式
  3. 給已有函數增加額外的功能

閉包和裝飾器的區分:

如果閉包函數的引數有且只有一個,並且是函數型別,那麼這個閉包函數稱之為裝飾器。

寫程式碼要遵循開放封閉原則,它規定已經實現的功能程式碼不允許被修改,但可以被擴充套件。

2. 裝飾器的範例程式碼

# 定義裝飾器
def decorator(func):
    def inner():
        # 在內部函數裡面對已有函數進行裝飾
        print('已新增登入認證')
        func()

    return inner


def comment():
    print('發表評論')


# 呼叫裝飾器對已有函數進行裝飾,左邊的comment=inner
comment = decorator(comment)

# 呼叫方式不變
comment()

3. 裝飾器的語法糖寫法

如果有多個函數都需要新增登入驗證的功能,每次都需要編寫func = decorator(func)這樣程式碼對已有函數進行裝飾,這種做法還是比較麻煩。

Python給提供了一個裝飾函數更加簡單的寫法,那就是語法糖,語法糖的書寫格式是: @裝飾器名字,通過語法糖的方式也可以完成對已有函數的裝飾

# 定義裝飾器
def decorator(func):
    def inner():
        # 在內部函數裡面對已有函數進行裝飾
        print('已新增登入認證')
        func()

    return inner


@decorator  # comment = decorator(comment) 裝飾器語法糖對該程式碼進行了封裝 左邊comment=inner
def comment():
    print('發表評論')


# 呼叫方式不變
comment()

4. 裝飾器的執行時機

當 當前模組載入完成以後,裝飾器會立即執行,對已有函數進行裝飾。

# 定義裝飾器
def decorator(func):
    print('裝飾器執行了')

    def inner():
        # 在內部函數裡面對已有函數進行裝飾
        print('已新增登入認證')
        func()

    return inner


@decorator  # comment = decorator(comment) 裝飾器語法糖對該程式碼進行了封裝 左邊comment=inner
def comment():
    print('發表評論')

執行結果:

裝飾器執行了

5. 裝飾器的使用

5.1 裝飾器的使用場景

  1. 函數執行時間的統計
  2. 輸出紀錄檔資訊

5.2 裝飾器實現已有函數執行時間的統計

import time


def decorator(func):
    def inner():
        # 獲取時間距離1970-1-1 0:0:1的時間差
        begin = time.time()
        func()
        end = time.time()
        result = end - begin
        print(f'函數執行完成耗時:{result}')

    return inner


@decorator
def work():
    for i in range(10000):
        print(i)


work()

6. 通用裝飾器的使用

通用裝飾器:可以裝飾任意型別的函數

使用裝飾器裝飾已有函數的時候,內部函數的型別和要裝飾的已有函數的型別保持一致

6.1 裝飾帶有引數的函數

def decorator(func):
    def inner(num1, num2):
        print('正在努力執行加法計算')
        func(num1, num2)

    return inner


@decorator
def add_num(num1, num2):
    result = num1 + num2
    print(f'結果為:{result}')


add_num(1, 2)

6.2 裝飾帶有引數、返回值的函數

def decorator(func):
    def inner(num1, num2):
        print('正在努力執行加法計算')
        num = func(num1, num2)
        return num

    return inner


@decorator
def add_num(num1, num2):
    result = num1 + num2
    return result


result = add_num(1, 2)
print(f'結果為:{result}')

6.3 裝飾帶有不定長引數、返回值的函數

def decorator(func):
    def inner(*args, **kwargs):
        print('正在努力執行加法計算')
        # *args:把元組裡面的每一個元素,按照位置引數的方式進行傳參
        # **kwargs:把字典裡面的每一個鍵值對,按照關鍵字的方式進行傳參
        num = func(*args, **kwargs)
        return num

    return inner


@decorator
def add_num(*args, **kwargs):
    result = 0
    for value in args:
        result += value
    for value in kwargs.values():
        result += value
    return result


result = add_num(1, 2, a=3)
print(f'結果為:{result}')

7. 多個裝飾器的使用

多個裝飾器的裝飾過程:由內到外的一個裝飾過程,先執行內部的裝飾器,在執行外部的裝飾器。

def make_div(func):
    print('make_div裝飾器執行了')

    def inner():
        result = '<div>' + func() + '</div>'
        return result

    return inner


def make_p(func):
    print('make_p裝飾器執行了')

    def inner():
        result = '<p>' + func() + '</p>'
        return result

    return inner


# 原理剖析:content = make_div(make_p(content))
# 分佈拆解:content = make_p(content),內部裝飾器完成,content = make_p.inner
#          content = make_div(make_p.inner)
@make_div
@make_p
def content():
    return '人生苦短,我用python'


c = content()
print(c)

make_p裝飾器執行了
make_div裝飾器執行了
<div><p>人生苦短,我用python</p></div>

8. 帶有引數的裝飾器

帶有引數的裝飾器就是使用裝飾器裝飾函數的時候可以傳入指定引數,語法格式: @裝飾器(引數,…)

使用帶有引數的裝飾器,其實是在裝飾器外面又包裹了一個函數,使用該函數接收引數,返回是裝飾器,因為 @ 符號需要配合裝飾器範例使用。

def return_decorator(flag):
    # 裝飾器只能接收一個引數並且是函數型別
    def decorator(func):
        def inner(a, b):
            if flag == '+':
                print('正在努力執行加法計算')
            elif flag == '-':
                print('正在努力執行減法計算')
            func(a, b)

        return inner

    # 當呼叫函數的時候可以返回一個裝飾器decorator
    return decorator


@return_decorator('+')  # decorator = return_decorator('+'), @decorator => add_num = decorator(add_num)
def add_num(a, b):
    result = a + b
    print(result)


@return_decorator('-')
def sub_num(a, b):
    result = a - b
    print(result)


add_num(1, 2)
sub_num(1, 2)

正在努力執行加法計算
3
正在努力執行減法計算
-1

9. 類裝飾器的使用

類裝飾器:使用類裝飾已有函數

class MyDecorator(object):
    def __init__(self, func):
        self.__func = func

    # 實現__call__方法,表示物件是一個可呼叫物件,可以像呼叫函數一樣進行呼叫
    def __call__(self, *args, **kwargs):
        # 對已有函數進行封裝
        print('馬上就有下班啦')
        self.__func()


@MyDecorator  # @MyDecorator => show = MyDecorator(show)
def show():
    print('快要下雪啦')


# 執行show,就相當於執行MyDecorator類建立的範例物件,show() => 物件()
show()

馬上就有下班啦
快要下雪啦

擴充套件:

函數之所以能夠呼叫,是因為函數內部實現了 __call__ 方法

10. 應用場景

收集函數的操作或錯誤紀錄檔記錄

驗證函數的使用許可權

計算函數的執行時間

在ORM/DB模型操作時,通過屬性方法動態地獲取關聯的資料

函數資料的快取

客製化函數的輸入和輸出(序列化和反序列化)