python 裝飾器

需求:

一個加法函數,想增強它的功能,能夠輸出加法函數的日誌信息

def add(x, y):
    return x + y

增加信息輸出功能:

def add(x, y):
    print("call add,x + y") #日誌輸出到控制台
    return x + y

上面的加法函數是完成了需求,但是有以下的缺點:

  打印日誌信息是一個功能,這條語句和 add 函數耦合太高;

  加法函數屬於業務功能,而輸出日誌信息的功能,屬於非業務功能代碼,不該放在業務函數 add 中;

下面代碼做到了業務功能分離,但是 fn 函數調用傳參是個問題:

def add(x, y):
    return x + y

def logger(fn):
    print('before')
    print('add function:{} {}'.format(4, 5))
    ret = fn(4, 5)
    print('after')
    return ret

print(logger(add))

解決了傳參的問題,進一步改變:

def add(x, y):
    return x + y

def logger(fn, *args, **kwargs):
    print('before')
    print('add function:{} | {}'.format(args, kwargs))
    ret = fn(*args, **kwargs)    # 參數解構
    print('after')
    return ret

print(logger(add, 4, 5))
print(logger(add, x=4, y=6))
print(logger(add, 4, y=15))

柯里化:

def add(x, y):
    return x + y

def logger(fn):
    def wrapper(*args, **kwargs):
        print('before')
        print('add function:{} | {}'.format(args, kwargs))
        ret = fn(*args, **kwargs)    # 參數解構
        print('after')
        return ret
    return wrapper

print(logger(add)(4, 5))
# 換一種寫法:
'''
add = logger(add)
print(add(x=5, y=10))
'''
# 1.先算等式右邊,logger(add)
# 2.add函數對象被fn記住
# 3.logger函數返回 wrapper,即add = wrapper
# 4.調用add(4, 5)即wrapper(4, 5)
# 5.ret = fn(*args, **kwargs),此處的fn記住的是最原始的add函數對象

裝飾器語法糖:

def logger(fn):
    def wrapper(*args, **kwargs):
        print('before')
        print('add function:{} | {}'.format(args, kwargs))
        ret = fn(*args, **kwargs)    # 參數解構
        print('after')
        return ret
    return wrapper

@logger    # 等價於add = logger(add)
def add(x, y):
    return x + y

print(add(20, 30))
'''
可以採用逆推思維:
1.add(20, 30)
2.包裝add(20, 30)為:wrapper(20, 30)
3.實現日誌增強功能:logger(add)(20, 30)
即:add(20, 30) => wrapper(20, 30) => logger(add)(20, 30)

def logger(fn):
    def wrapper(*args, **kwargs):
        val = fn(*args, **kwargs)
        return val
    return wrapper
@logger
def add(x, y):
    return x + y
'''

@logger是什麼?這就是裝飾器語法

裝飾器(無參)

  它是一個函數

  函數作為它的形參,無參裝飾器實際上就是一個單形參函數

  返回值也是一個函數 可以使用 @functionname 方式,簡化調用

  註:此處裝飾器的定義只是就目前所學的總結,並不準確,只是方便理解

裝飾器和高階函數

  裝飾器可以是高階函數,但裝飾器是對傳入函數的功能的裝飾(功能增強)

 

Tags: