帶引數的 Python 裝飾器讓你的程式碼更優雅

单身合约發表於2024-11-10

引言
在上一篇文章中,我們介紹了 Python 裝飾器的基本概念及其簡單用法。

前面講到的裝飾器都是不帶引數的裝飾器,在需要對裝飾器做一些針對性的處理的時候就不太適用了,這個時候需要對裝飾器傳入一些引數,根據傳入的引數進行不同的處理。

帶引數裝飾器在實際開發中能夠靈活地調整函式行為,廣泛應用於日誌記錄、許可權驗證和快取等場景。。

如何定義帶引數的裝飾器
帶引數的裝飾器的結構稍微複雜一些。我們要定義一個外層函式來接收裝飾器的引數,並在這個外層函式中定義實際的裝飾器函式,相當於將之前的裝飾器用一個正常的函式包裝了一層。下面來看一下帶引數的裝飾器和不帶引數的裝飾器的對比。

不帶引數的裝飾器

def decorator(func):
def wrapper():
print(f"Decorator")
return func()

return wrapper

帶引數的裝飾器:

def decorator_with_args(arg1=None, arg2=None):
def decorator(func):
def wrapper():
print(f"Decorator arguments: {arg1}, {arg2}")
return func()

    return wrapper

return decorator

在上面帶引數的裝飾器中,decorator_with_args 是外層裝飾器函式,它接收兩個引數 arg1 和 arg2。內部的 decorator 函式是實際的裝飾器,而 wrapper 函式則是用來包裝被裝飾的函式。

如何使用帶引數的裝飾器
@decorator_with_args("Hello", "World")
def greet():
print(f"Greeting name")

greet()

輸出:

Decorator arguments: Hello, World
Greeting name

在這個例子中,greet 函式被 decorator_with_args 裝飾,傳入了兩個引數。裝飾器在呼叫 greet 函式之前,先列印了裝飾器的引數。

注意:帶引數的裝飾器有兩個可選引數,可以選擇不傳。但需要注意的是,@decorator_with_args() 不能簡寫為 @decorator_with_args,因為 decorator_with_args 本質上是一個函式,必須呼叫後才能生效。

兩種裝飾器用法區別
使用不帶引數的裝飾器時,用法如下,要注意區別。

def decorator(func):
def wrapper():
print(f"Decorator")
return func()

return wrapper

@decorator
def greet():
print(f"Greeting name")

greet()

處理可變引數
在很多時候,我們會遇到被裝飾的函式有引數的情況,這時要怎麼處理呢。

為了解決這個問題,我們可以使用 *args 和 **kwargs 來接收函式的所有引數。

裝飾器處理可變引數

def func_decorator(decorator_arg):
def decorator(func):
def wrapper(args, **kwargs):
print(f"Decorator argument: {decorator_arg}")
print(f"Function arguments: {args}, {kwargs}")
return func(
args, **kwargs)

    return wrapper

return decorator

@func_decorator("Test")
def add(*args):
return sum(args)

result = add(1, 2, 3, 4)
print(f"Result: {result}")

輸出:

Decorator argument: Test
Function arguments: (1, 2, 3, 4), {}
Result: 10

在這個示例中,func_decorator 裝飾器可以處理在被裝飾的函式有引數的情況,並且可以自適應任何引數,裝飾器能夠正確處理這些引數。

帶引數裝飾器的使用案例
自定義日誌格式
在實際開發中,我們常常需要記錄函式的執行日誌。透過帶引數的裝飾器,我們可以自定義被裝飾函式的引數和返回值,甚至可以做到修改被裝飾函式的引數和返回值。

def log_decorator(fun_args=False, fun_result=False):
def decorator(func):
def wrapper(args, **kwargs):
if fun_args:
print(f'func args is: {args} {kwargs}')
if fun_result:
result = func(
args, **kwargs)
print(f'func result is: {result}')
return result
else:
return func(*args, **kwargs)

    return wrapper

return decorator

@log_decorator(True, True)
def multiply(x, y):
return x * y

print(f"Result: {multiply(3, 5)}")

@log_decorator(False, False)
def multiply(x, y):
return x * y

print(f"Result: {multiply(3, 5)}")

結果:

func args is: (3, 5) {}
func result is: 15
Result: 15

Result: 15

在這個示例中,log_decorator 裝飾器接受兩個布林型引數,用於判斷是否要輸出函式引數和函式返回值。

此處日誌輸出比較簡單,僅做為示例參考,生產環境要做一些比較複雜的判斷並且要將日誌持久化到磁碟的日誌檔案中。

快取機制
import time
from functools import lru_cache

class Timer:
def init(self):
self.start = None

def __enter__(self):
    import time
    self.start = time.time()

def __exit__(self, type, value, traceback):
    print(f'time is:{time.time() - self.start}')

@lru_cache(maxsize=20)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

with Timer() as timer:
print(fibonacci(15))

with Timer() as timer2:
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(15))

chof.cdhgs.com
chof.borkjc.com
chof.huoyiyingshi.com
chof.chinachaiyao.com
chof.njhhcw.com
chof.kangshungcsl.com
chof.haixiaocdn.com
chof.jszcxny.com
chof.baiyichi.com
chof.chengducxwc.com
chof.seemlgo.com
chof.shyachao.com
chof.dhl-d.com
chof.tajqzl.com
chof.hzsunbyte.com
chof.tjzxjy.com
chof.qrpress.com
chof.zhanhao118.com
chof.marsdrinkschina.com
chof.xjlqcl.com
chof.5000news.com
chof.2500trip.com
chof.htsyfs.com
chof.hmgjjd.com
chof.tcccpsb.com
chof.hfsmttp.com
chof.nnlianbao.com
chof.xinchengabc.com
chof.njns56.com
chof.gzlcjzs.com
chof.xinlvchuang.com
chof.juanbanjicj.com
chof.tongtaijituan.com
chof.lydsmp.com
chof.shinenergygroup.com
chof.jingranchimian.com
chof.tj-shengming.com
chof.njxlkhs.com
chof.shzljr.com
chof.guanlinyouxuan.com
chof.ynyoutu.com
chof.jiadalong.com
chof.jumeizi.com
chof.zhs888.com
chof.cnaoxin.com
chof.afu365.com
chof.tzhyex.com
chof.xincaiit.com
chof.senmaohuanbao.com
chof.bjqunlemuye.com

結果:

14 13
13 12
12 11
11 10
10 9
9 8
8 7
7 6
6 5
5 4
4 3
3 2
2 1
1 0
610
time is:4.076957702636719e-05

14 13
13 12
12 11
11 10
10 9
9 8
8 7
7 6
--- 此處資料量太大,省略,完整的輸出大約 1000 行,大家可以自行執行檢視結果
1 0
610
time is:0.002106189727783203

此處我使用了 functools 包中提供的 lru_cache 快取裝飾器,用於快取在計算斐波那契數列時重複的計算值。

從執行結果可以看到,不使用裝飾器時,函式進行了大量的重複計算,導致最後的執行耗時和使用了裝飾器的結果差了好幾個數量級。

總結
帶引數的裝飾器為我們提供了極大的靈活性,使得我們可以根據不同的需求來調整函式的行為。在實際應用中,帶引數的裝飾器可以用於多種場景,如自定義日誌、快取、控制函式的執行行為等等。

相關文章