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

Tags: