tft每日頭條

 > 生活

 > python的八種裝飾器

python的八種裝飾器

生活 更新时间:2025-01-24 01:52:19

選自pouannes.github.io

作者:Pierre Ouannes

編譯:機器之心(almosthuman2014)

原文:https://pouannes.github.io/blog/decorators/

Python 是一種對新手很友好的語言。但是,它也有很多較難掌握的高級功能,比如裝飾器(decorator)。很多初學者一直不理解裝飾器及其工作原理,在這篇文章中,我們将介紹裝飾器的來龍去脈。

在 Python 中,函數是一種非常靈活的結構,我們可以把它賦值給變量、當作參數傳遞給另一個函數,或者當成某個函數的輸出。裝飾器本質上也是一種函數,它可以讓其它函數在不經過修改的情況下增加一些功能。

這也就是「裝飾」的意義,這種「裝飾」本身代表着一種功能,如果用它修飾不同的函數,那麼也就是為這些函數增加這種功能。

一般而言,我們可以使用裝飾器提供的 @ 語法糖(Syntactic Sugar)來修飾其它函數或對象。如下所示我們用 @dec 裝飾器修飾函數 func :

@dec

def func:

pass

理解裝飾器的最好方式是了解裝飾器解決什麼問題,本文将從具體問題出發一步步引出裝飾器,并展示它的優雅與強大。

設置問題

為了解裝飾器的目的,接下來我們來看一個簡單的示例。假如你有一個簡單的加法函數 dec.py,第二個參數的默認值為 10:

# dec.py

def add(x, y=10):

return x y

我們來更認真地看一下這個加法函數:

>>> add(10, 20)

30

>>> add

<function add at 0x7fce0da2fe18>

>>> add.__name__

add

>>> add.__module__

__main__

>>> add.__defaults__ # default value of the `add` function

(10,)

>>> add.__code__.co_varnames # the variable names of the `add` function

( x , y )

我們無需理解這些都是什麼,隻需要記住 Python 中的每個函數都是對象,它們有各種屬性和方法。你還可以通過 inspect 模塊查看 add 函數的源代碼:

>>> from inspect import getsource

>>> print(getsource(add))

def add(x, y=10):

return x y

現在你以某種方式使用該加法函數,比如你使用一些操作來測試該函數:

# dec.py

from time import time

def add(x, y=10):

return x y

print( add(10) , add(10))

print( add(20, 30) , add(20, 30))

print( add("a", "b") , add("a", "b"))

Output: i

add(10) 20

add(20, 30) 50

add("a", "b") ab

假如你想了解每個操作的時間,可以調用 time 模塊:

# dec.py

from time import time

def add(x, y=10):

return x y

before = time

print( add(10) , add(10))

after = time

print( time taken: , after - before)

before = time

print( add(20, 30) , add(20, 30))

after = time

print( time taken: , after - before)

before = time

print( add("a", "b") , add("a", "b"))

after = time

print( time taken: , after - before)

Output:

add(10) 20

time taken: 6.699562072753906e-05

add(20, 30) 50

time taken: 6.9141387939453125e-06

add("a", "b") ab

time taken: 6.9141387939453125e-06

現在,你作為一個編程人員是不是有些手癢,畢竟我們不喜歡總是複制粘貼相同的代碼。現在的代碼可讀性不強,如果你想改變什麼,你就得修改所有出現的地方,Python 肯定有更好的方式。

我們可以按照如下做法,直接在 add 函數中捕捉運行時間:

# dec.py

from time import time

def add(x, y=10):

before = time

rv = x y

after = time

print( time taken: , after - before)

return rv

print( add(10) , add(10))

print( add(20, 30) , add(20, 30))

print( add("a", "b") , add("a", "b"))

這種方法肯定比前一種要好。但是如果你還有另一個函數,那麼這似乎就不方便了。當我們有多個函數時:

# dec.py

from time import time

def add(x, y=10):

before = time

rv = x y

after = time

print( time taken: , after - before)

return rv

def sub(x, y=10):

return x - y

print( add(10) , add(10))

print( add(20, 30) , add(20, 30))

print( add("a", "b") , add("a", "b"))

print( sub(10) , sub(10))

print( sub(20, 30) , sub(20, 30))

因為 add 和 sub 都是函數,我們可以利用這一點寫一個 timer 函數。我們希望 timer 能計算一個函數的運算時間:

def timer(func, x, y=10):

before = time

rv = func(x, y)

after = time

print( time taken: , after - before)

return rv

這很不錯,不過我們必須使用 timer 函數包裝不同的函數,如下所示:

print( add(10) , timer(add,10)))

現在默認值還是 10 嗎?未必。那麼如何做得更好呢?

這裡有一個主意:創建一個新的 timer 函數,并包裝其他函數,然後返回包裝後的函數:

def timer(func):

def f(x, y=10):

before = time

rv = func(x, y)

after = time

print( time taken: , after - before)

return rv

return f

現在,你隻需用 timer 包裝一下 add 和 sub 函數 :

add = timer(add)

這樣就可以了!以下是完整代碼:

# dec.py

from time import time

def timer(func):

def f(x, y=10):

before = time

rv = func(x, y)

after = time

print( time taken: , after - before)

return rv

return f

def add(x, y=10):

return x y

add = timer(add)

def sub(x, y=10):

return x - y

sub = timer(sub)

print( add(10) , add(10))

print( add(20, 30) , add(20, 30))

print( add("a", "b") , add("a", "b"))

print( sub(10) , sub(10))

print( sub(20, 30) , sub(20, 30))

Output:

time taken: 0.0

add(10) 20

time taken: 9.5367431640625e-07

add(20, 30) 50

time taken: 0.0

add("a", "b") ab

time taken: 9.5367431640625e-07

sub(10) 0

time taken: 9.5367431640625e-07

sub(20, 30) -10

我們來總結一下這個過程:我們有一個函數(比如 add 函數),然後用一個動作(比如計時)包裝該函數。包裝的結果是一個新函數,能實現某些新功能。

當然了,默認值還有點問題,稍後我們會解決它。

裝飾器

現在,上面的解決方案以及非常接近裝飾器的思想了,使用常見行為包裝某個具體的函數,這種模式就是裝飾器在做的事。使用裝飾器後的代碼是:

def add(x, y=10):

return x y

add = timer(add)

You write:

@timer

def add(x, y=10):

return x y

它們的作用是一樣的,這就是 Python 裝飾器的作用。它實現的作用類似于 add = timer(add),隻不過裝飾器把句法放在函數上面,且句法更加簡單:@timer。

# dec.py

from time import time

def timer(func):

def f(x, y=10):

before = time

rv = func(x, y)

after = time

print( time taken: , after - before)

return rv

return f

@timer

def add(x, y=10):

return x y

@timer

def sub(x, y=10):

return x - y

print( add(10) , add(10))

print( add(20, 30) , add(20, 30))

print( add("a", "b") , add("a", "b"))

print( sub(10) , sub(10))

print( sub(20, 30) , sub(20, 30))

參數和關鍵字參數

現在,還有一個小問題沒有解決。在 timer 函數中,我們将參數 x 和 y 寫死了,即指定 y 的默認值為 10。有一種方法可以傳輸該函數的參數和關鍵字參數,即 *args 和 **kwargs。參數是函數的标準參數(在本例中 x 為參數),關鍵字參數是已具備默認值的參數(本例中是 y=10)。代碼如下:

# dec.py

from time import time

def timer(func):

def f(*args, **kwargs):

before = time

rv = func(*args, **kwargs)

after = time

print( time taken: , after - before)

return rv

return f

@timer

def add(x, y=10):

return x y

@timer

def sub(x, y=10):

return x - y

print( add(10) , add(10))

print( add(20, 30) , add(20, 30))

print( add("a", "b") , add("a", "b"))

print( sub(10) , sub(10))

print( sub(20, 30) , sub(20, 30))

現在,該 timer 函數可以處理任意函數、任意參數和任意默認值設置了,因為它僅僅将這些參數傳輸到函數中。

高階裝飾器

你們可能會疑惑:如果我們可以用一個函數包裝另一個函數來添加有用的行為,那麼我們可以再進一步嗎?我們用一個函數包裝另一個函數,再被另一個函數包裝嗎?

可以!事實上,函數的深度可以随你的意。例如,你想寫一個裝飾器來執行某個函數 n 次。如下所示:

def ntimes(n):

def inner(f):

def wrapper(*args, **kwargs):

for _ in range(n):

rv = f(*args, **kwargs)

return rv

return wrapper

return inner

然後你可以使用上述函數包裝另一個函數,例如前文中的 add 函數:

@ntimes(3)

def add(x, y):

print(x y)

return x y

輸出的語句表明該代碼确實執行了 3 次。

python的八種裝飾器(讀懂系列一文讀懂Python裝飾器)1

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2025 - www.tftnews.com All Rights Reserved