Python學習筆記(二十二):裝飾器


裝飾器:

如果有一些函數,已經實現了某些功能,要求在不修改這些函數源代碼,並且調用名稱不變的前提下,對這些函數的功能進行一些擴展,就可以使用裝飾器來實現;

比如存在如下兩個函數:

# 定義兩個函數
def test1():
    print("=== test1 ===")

def test2():
    print("=== test2 ===")

# 調用函數
test1()
test2()

要求不能修改原函數的代碼,以及調用名稱不變的情況下,對函數功能進行擴展:

# 定義一個通用函數,參數用於接收原函數的引用
def test(func):

    # 定義一個閉包函數,在閉包中對原函數功能進行擴展
    def inner():
        print("=== 擴展的功能 ===")
        func()  # 調用原函數

    # 返回閉包函數的引用
    return inner

# 定義兩個函數
def test1():
    print("=== test1 ===")

def test2():
    print("=== test2 ===")

# 調用通用函數,將原函數的引用傳入,返回閉包函數的引用
test1 = test(test1)
# 通過閉包函數的引用調用閉包函數,在閉包函數中實現對
# 原函數的擴展,然後再調用原函數;
# 注意:此時的 test1() 不再是直接調用 test1 函數了,而是調用的閉包函數;
test1()

print("==========================")

test2 = test(test2)
test2()

輸出結果:

裝飾器原理的解釋:

1、當 python 解析器執行到 test1 = test(test1) 代碼的時候,調用通用函數 test(),然後將原函數 test1 的引用傳入,用參數 func 接收;那麼此時,test() 函數中的參數 func 也變成了 test1 原函數的引用;

2、 test() 函數執行結束,返回閉包函數的引用(return inner),用變量 test1 接收;那麼此時,變量 test1 不再指向原函數 test1 了,而是指向閉包函數 inner() 了;

3、最後調用 test1() 的時候,調用的就是閉包函數 inner() 了;在閉包函數 inner() 中實現了擴展的功能,然後在調用 func() 函數;從 1 我們已經知道,此時變量 func 是原函數 test1 的引用;

上面介紹的是裝飾器的原理,裝飾器的功能代碼 test1 = test(test1) 可以直接在 test() 函數頭上加一個 @test 裝飾器來實現:

# 定義一個通用函數,參數用於接收原函數的引用
def test(func):

    # 定義一個閉包函數,在閉包中對原函數功能進行擴展
    def innner():
        print("=== 擴展的功能 ===")
        func()  # 調用原函數

    # 返回閉包函數的引用
    return innner

# 定義兩個函數
# 裝飾器 @test 就相當於 test1 = test(test1)
@test
def test1():
    print("=== test1 ===")

@test
def test2():
    print("=== test2 ===")

# 調用函數
test1()
test2()

 

多個裝飾器的使用順序:

# 定義一個裝飾器函數:功能是對原函數的返回值進行加粗設置
def makeBold(fn):
    # 定義一個閉包函數
    def wrapped():
        return "<b>" + fn() + "</b>"
    # 返回閉包的引用
    return wrapped

# 定義一個裝飾器函數:功能是對原函數的返回值進行斜體設置
def makeItalic(fn):
    # 定義一個閉包函數
    def wrapped():
        return "<i>" + fn() + "</i>"
    # 返回閉包的引用
    return wrapped

# 定義原函數,使用裝飾器
@makeBold   # 相當於 test = makeBold(test)
@makeItalic # 相當於 test = makeItalic(test)
def test():
    return "hello world"

# 調用函數
print(test())

輸出結果:

從結果可以看出,當使用多個裝飾器的時候,是從下往上的順序依次調用的;

 

裝飾有參函數:

對定長參數的函數進行裝飾:

# 定義一個裝飾器函數
def test(fn):
    # 閉包函數也要接受兩個參數
    def test_in(a_in, b_in):
        print("=== 擴展的功能 ===")
        # 調用原函數的時候把參數傳入
        fn(a_in, b_in)
    return test_in

# 對有參函數進行裝飾(原函數含有兩個參數)
# @test 相當於 test1 = test(test1)
@test
def test1(a, b):
    print("a = %d, b = %d" %(a, b))

# 調用函數:此時 test1 是裝飾器中閉包函數的引用
test1(22, 33)

對不定長參數的函數進行裝飾:

# 定義一個裝飾器函數
def test(fn):
    # 閉包函數:*args 表示以元組的方式接收不定長參數
    def test_in(*args):
        print("=== 擴展的功能 ===")
        # 調用原函數的時候把參數傳入
        fn(*args)
    return test_in

# 對有參函數進行裝飾(原函數含有兩個參數)
# @test 相當於 test1 = test(test1)
@test
def test1(a, b):
    print("a = %d, b = %d" %(a, b))

@test
def test2(a, b, c, d):
    print("a = %d, b = %d, c = %d, d = %d" %(a, b, c, d))

# 調用函數:此時 test1、test2 都是裝飾器中閉包函數的引用
test1(22, 33)
test2(22, 33, 44, 55)

 

通用裝飾器:

# 定義一個通用裝飾器函數
def test(fn):
    # 閉包函數:*args 表示以元組的方式接收不定長參數
    def test_in(*args):
        print("=== 擴展的功能 ===")
        # 調用原函數的時候把參數傳入,並返回原函數的返回值
        return fn(*args)
    # 返回閉包函數的引用
    return test_in

# 對普通函數進行裝飾
@test
def test1():
    print("=== test1 ===")

# 對有返回值函數進行裝飾
@test
def test2():
    print("=== test2 ===")
    return "hello world"

# 對有參函數進行裝飾
@test
def test3(a, b, c):
    print("=== test3 ===")
    print("a = %d, b = %d, c = %d" %(a, b, c))

# 調用普通函數
test1()
print("****************************")

# 調用有返回值的函數
print(test2())
print("****************************")

# 調用有參函數
test3(11, 22, 33)
print("****************************")

 

帶參數的裝飾器:

需要在裝飾器函數外面再套一個函數,專門用來接收裝飾器的參數;

# 在裝飾器外面再套一個函數,用於接收裝飾器的參數
def test_arg(arg):

    # 定義一個通用裝飾器函數(此時的裝飾器函數是一個閉包)
    def test(fn):
        # 閉包函數:*args 表示以元組的方式接收不定長參數
        def test_in(*args):
            print("裝飾器的參數 arg:%s" %arg)
            print("=== 記錄日誌 ===")
            # 調用原函數的時候把參數傳入,並返回原函數的返回值
            return fn(*args)
        # 返回閉包函數的引用
        return test_in

    # 返回裝飾器函數的引用
    return test

# @test_arg("hello"):先執行 test_arg("hello") 函數,返回裝飾器函數 test 的引用,
# 此時裝飾器就變成了 @test;然後就可以像普通的裝飾器一樣使用了;
@test_arg("hello")
def test1():
    print("=== test1 ===")

# 調用函數
test1()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章