python裝飾器理解以及常用的裝飾器介紹
這裡這個整理一下python中的裝飾器的用法,以及在看程式碼時經常會看到的一些常用裝飾器.
一. 裝飾器
必須記住的幾點:
- 裝飾器能將被裝飾的函數替換為其他的函數(下邊例子的第三行的輸出結果顯示)
- 裝飾器在載入模組的時候立即執行 ** (下邊例子的前兩行的輸出**結果)
- 裝飾器一般在一個模組中定義,在另外一個模組中使用 (下方例子的簡單示例所示的組織方式)
- 裝飾器可以帶有參數,這就是參數化裝飾器,也就是裝飾器的工廠函數 (第二個例子所示的參數化裝飾器)
一個簡單的裝飾器的例子:
# 裝飾器的定義文件clock_register.py, 裝飾器的作用為列印函數運行時間,傳入的參數以及調用的結果返回.
import time
def clock(func):
print('Decroate the function:', func.__name__)
def clocked(*args):
t0 = time.perf_counter() # 作為初始時間,構成下文中的elapsed(函數運行的消耗時間.)
result = func(*args)# 被裝飾函數的執行
elapsed = time.perf_counter() - t0
name = func.__name__ # 裝飾器所修飾的函數的名稱
arg_str = ', '.join(repr(arg) for arg in args) # 傳入參數的字元格式
# 格式化輸出,分別列印函數的運行時間, 函數名稱, 傳入的參數, 函數計算結果
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
###### clock裝飾器的調用模組
from clock_register import clock
import time
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
if n < 2:
return 1
return n * factorial(n-1)
if __name__ == '__main__':
print(snooze)
print('*' * 40, 'calling snooze(.123)')
snooze(.123)
print('*' * 40, 'calling factorial(6)')
print('6!=', factorial(6))
輸出結果:
前兩行的輸出結果說明: 裝飾器在載入模組時立即執行
第三行的輸出結果顯示: 裝飾器能將被裝飾的函數替換為其他的函數, 此時的snooze函數為clocked函數,傳入的args=0.123
為clock的參數.
Decroate the function: snooze
Decroate the function: factorial
<function clock.<locals>.clocked at 0x7f770f855b00>
**************************************** calling snooze(.123)
[0.12307592s] snooze(0.123) -> None
**************************************** calling factorial(6)
[0.00000098s] factorial(1) -> 1
[0.00002348s] factorial(2) -> 2
[0.00003814s] factorial(3) -> 6
[0.00005152s] factorial(4) -> 24
[0.00006555s] factorial(5) -> 120
[0.00008150s] factorial(6) -> 720
6!= 720
參數化的裝飾器的例子
一個簡單的例子
### 參數化的裝飾器
DEFAULT_FMT = '[{elapsed}s] {name}({args}) -> {result}'
def clock_args(fmt = DEFAULT_FMT):
def decroate(func):
print('Decroate the function:', func.__name__)
def clocked(*args):
t0 = time.perf_counter() # 作為初始時間,構成下文中的elapsed(函數運行的消耗時間.)
result = func(*args)# 被裝飾函數的執行
elapsed = time.perf_counter() - t0
name = func.__name__ # 裝飾器所修飾的函數的名稱
arg_str = ', '.join(repr(arg) for arg in args) # 傳入參數的字元格式
# 格式化輸出,按照參數化,列印指定的輸出格式, 這裡採用**locals()獲取局部參數
print(fmt.format(**locals()))
return result
return clocked
return decroate
####### 參數化裝飾器的調用
from clock_register import clock, DEFAULT_FMT, clock_args
import time
@clock_args()
def snooze(seconds):
time.sleep(seconds)
@clock_args(fmt='{name}: {elapsed}s')
def snooze1(seconds):
time.sleep(seconds)
if __name__ == '__main__':
print(snooze1)
print('*' * 40, 'calling snooze(.123)')
snooze(.123)
print('*' * 40, 'calling snooze(.123)')
snooze1(.123)
最終的輸出結果:
Decroate the function: snooze
Decroate the function: snooze1
<function clock_args.<locals>.decroate.<locals>.clocked at 0x7f770f84f0e0>
**************************************** calling snooze(.123)
[0.1231747239944525s] snooze((0.123,)) -> None
(base) walle@walle-All-Series:~/123$ python clock_test.py
Decroate the function: snooze
Decroate the function: snooze1
**************************************** calling snooze(.123)
[0.12314573698677123s] snooze((0.123,)) -> None
**************************************** calling snooze(.123)
snooze1: 0.12315590702928603s
二. 常用的裝飾器
@property
參考這篇文章:@property裝飾器的簡單理解
@classmethod與@staticmethod
這兩個裝飾器在很多的程式碼中經常會看到,@classmethod叫類方法, @staticmethod叫靜態方法,下邊以一個簡單的例子分析這兩個迭代器的用法以及一些區別.
簡單的例子:
class A:
def __init__(self):
pass
# 實例方法, 約定俗成第一個參數為self, 與實例化對象綁定
def m1(self, n):
print("self: ", self)
# 類方法, 約定俗成第一個參數為 cls, 與類綁定
@classmethod
def m2(cls, n):
print('cls: ', cls)
# 靜態方法(其實就是一個普通的函數,只是位於類中,與類和實例均沒有綁定關係)
@staticmethod
def m3(n):
print('this is a static method.')
a = A()
a.m1(1)
a.m2(1)
A.m2(1)
A.m3(1)
a.m3(1)
m3(1)
輸出結果為:
self: <__main__.A object at 0x7f9a9eb50e90>
cls: <class '__main__.A'>
cls: <class '__main__.A'>
this is a static method.
this is a static method.
Traceback (most recent call last):
File "classmethod_staticmethod.py", line 24, in <module>
m3(1)
NameError: name 'm3' is not defined
依據以上結果可以看出:
a = A() # 實例化一個實例對象
a.m1(1) # m1是一個實例方法,通過實例a調用
a.m2(1) # m2是一個類方法,通過實例a找到類A,然後通過A調用m2
A.m2(1) # 利用類直接調用類方法.
A.m3(1) # 靜態方法,既可以直接使用類A調用,也可以使用實例a調用
a.m3(1)
m3(1) # 不可直接調用
@functools.lru_cache
學作業系統的時候lru這個東西經常出現,在leetcode上也有實現lru的這個題目,這個裝飾器可以實現這種功能.
例如一個求斐波那契數列的函數:
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
if __name__ == '__main__':
print(fibonacci(6))
結果為:
[0.00000133s] fibonacci(0) -> 0
[0.00000093s] fibonacci(1) -> 1
[0.00004139s] fibonacci(2) -> 1
[0.00000071s] fibonacci(1) -> 1
[0.00000082s] fibonacci(0) -> 0
[0.00000069s] fibonacci(1) -> 1
[0.00002819s] fibonacci(2) -> 1
[0.00005552s] fibonacci(3) -> 2
[0.00012448s] fibonacci(4) -> 3
[0.00000067s] fibonacci(1) -> 1
[0.00000065s] fibonacci(0) -> 0
[0.00000071s] fibonacci(1) -> 1
[0.00002738s] fibonacci(2) -> 1
[0.00005426s] fibonacci(3) -> 2
[0.00000063s] fibonacci(0) -> 0
[0.00000068s] fibonacci(1) -> 1
[0.00002711s] fibonacci(2) -> 1
[0.00000063s] fibonacci(1) -> 1
[0.00000079s] fibonacci(0) -> 0
[0.00000068s] fibonacci(1) -> 1
[0.00002817s] fibonacci(2) -> 1
[0.00005480s] fibonacci(3) -> 2
[0.00010871s] fibonacci(4) -> 3
[0.00018944s] fibonacci(5) -> 5
[0.00034368s] fibonacci(6) -> 8
8
如果使用lru裝飾器:
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
這個裝飾器實現了LRU(備忘錄)的功能,減少了重複計算.
結果為:
[0.00000118s] fibonacci(0) -> 0
[0.00000085s] fibonacci(1) -> 1
[0.00004799s] fibonacci(2) -> 1
[0.00000145s] fibonacci(3) -> 2
[0.00007905s] fibonacci(4) -> 3
[0.00000122s] fibonacci(5) -> 5
[0.00011034s] fibonacci(6) -> 8
8
其中有裝飾器的疊放的操作,裝飾器的疊放的簡單例子:
@d1
@d2
def f():
print('f')
等價於:
def f():
print('f')
f = d1(d2(f))
更多編程筆記文章: //github.com/lxztju/notes