iT邦幫忙

2

難以理解的python裝飾器語法

今天嘗試整理python裝飾器語法的筆記,
這部分自己找資源讀蠻久的,
一直看的沒有很懂,
大概語法用的還蠻深的,
嘗試整理一下裝飾器語法的用途

基礎語法

裝飾器的語法大致上如下:

@decorator
def function(): 
    # doSomething

function是一個函數,
會用到的情境大概是我想要幫函數擴充一些功能,
但是又不想直接修改函數本身,
原因是如果每個函數都想新增類似的功能,
會寫很多重複的代碼,
因此我透過decorator這個函數修改function
語言簡寫為@decorator
就好像拿@decorator去裝飾function一樣,
語意相當於function = decorator(function)

所以我們另外需要定義一個函數叫decorator
參數放一個函數,回傳一個改造過的函數

在參考資料一中,有這樣一段話生動的描述了裝飾器的概念

每個人都有的內褲主要功能是用來遮羞,但是到了冬天它沒法為我們防風禦寒,咋辦?我們想到的一個辦法就是把內褲改造一下,讓它變得更厚更長,這樣一來,它不僅有遮羞功能,還能提供保暖,不過有個問題,這個內褲被我們改造成了長褲後,雖然還有遮羞功能,但本質上它不再是一條真正的內褲了。於是聰明的人們發明長褲,在不影響內褲的前提下,直接把長褲套在了內褲外面,這樣內褲還是內褲,有了長褲後寶寶再也不冷了。裝飾器就像我們這里說的長褲,在不影響內褲作用的前提下,給我們的身子提供了保暖的功效。

例子- 測量函數執行的時間

比方說今天我心血來潮寫了三個判斷一個數字是不是質數的函數:

def isPrime_V1(n):
    return n>=2 and len([i for i in range(1,int(n**0.5)+1) if n%i==0])==1

def isPrime_V2(n):
    return n>=2 and not any(n%i==0 for i in range(2,int(n**0.5+1)))

def isPrime_V3(n):
    return n==2 or (n > 2 and n%2 and all(n%x for x in range(3,int(n**.5)+1,2)))

我想要知道那一種方法做的比較快,
便可以用time模組測量一段程式碼的時間

最簡單的方法為:

import time

def isPrime_V1(n):
    return n>=2 and len([i for i in range(1,int(n**0.5)+1) if n%i==0])==1

def isPrime_V2(n):
    return n>=2 and not any(n%i==0 for i in range(2,int(n**0.5+1)))

def isPrime_V3(n):
    return n==2 or (n > 2 and n%2 and all(n%x for x in range(3,int(n**.5)+1,2)))

tStart = time.time() #計時開始
print(isPrime_V1(2**31-1))
tEnd = time.time() #計時結束
print(f"第一個函數執行了{(tEnd - tStart):.4f}秒")

tStart = time.time() #計時開始
print(isPrime_V2(2**31-1))
tEnd = time.time() #計時結束
print(f"第二個函數執行了{(tEnd - tStart):.4f}秒")

tStart = time.time() #計時開始
print(isPrime_V3(2**31-1))
tEnd = time.time() #計時結束
print(f"第三個函數執行了{(tEnd - tStart):.4f}秒")

意思是分別拿三個函數去判斷一個大數(比如說2的31次方-1)這個數字是不是質數,
比較時間長短,
但是這樣寫的缺點便是重複的程式碼很多

改良的方法是把「測量時間」這件事情用函數包起來:

import time
def measureTime(func, i):
    tStart = time.time() #計時開始
    print(func(i))
    tEnd = time.time() #計時結束
    print(f"{func.__name__} 總共執行了{(tEnd - tStart):.4f}秒")

def isPrime_V1(n):
    return n>=2 and len([i for i in range(1,int(n**0.5)+1) if n%i==0])==1

def isPrime_V2(n):
    return n>=2 and not any(n%i==0 for i in range(2,int(n**0.5+1)))

def isPrime_V3(n):
    return n==2 or (n > 2 and n%2 and all(n%x for x in range(3,int(n**.5)+1,2)))

measureTime(isPrime_V1, 2**31-1)
measureTime(isPrime_V2, 2**31-1)
measureTime(isPrime_V3, 2**31-1)

但是就參考資料一的邏輯來說,
我們的主要功能是「判斷質數」,
「測量時間」是附加功能,
仍然希望用isPrime_V1(2**31-1)的方式來呼叫函數

那用裝飾器語法怎麼寫呢?
我們一樣定義measureTime函數,
但是回傳一個新的內部函數inner
這樣就可以把原來的函數改造了

像這樣:

import time
def measureTime(func):
    def inner(*args): 
        tStart = time.time() #計時開始
        res = func(*args)
        tEnd = time.time() #計時結束
        print(f"{func.__name__} 總共執行了{(tEnd - tStart):.4f}秒")
        return res
    return inner

@measureTime
def isPrime_V1(n):
    return n>=2 and len([i for i in range(1,int(n**0.5)+1) if n%i==0])==1

@measureTime
def isPrime_V2(n):
    return n>=2 and not any(n%i==0 for i in range(2,int(n**0.5+1)))

@measureTime
def isPrime_V3(n):
    return n==2 or (n > 2 and n%2 and all(n%x for x in range(3,int(n**.5)+1,2)))

print(isPrime_V1(2**31-1))
print(isPrime_V2(2**31-1))
print(isPrime_V3(2**31-1))

心得:
自己看裝飾器語法的時候一直看不太懂,
自己打過程式之後總算比較懂了,
果然程式還是要多實作多測試才行呢

參考資料

  1. 理解 Python 裝飾器看這一篇就夠了
  2. geeksforgeeks- Decorators in Python

1 則留言

1
samuel24
iT邦新手 5 級 ‧ 2020-07-05 08:27:46

舉手發問!!~

請問在最後一段程式碼中,只有 isPrime_V1(n) 上有加裝飾器,那麼 isPrime_V2(n)isPrime_V3(n) 也會被「裝飾」到嗎?如果是的話,請問要如何阻止它繼續影響後面的函數?(如何做到不改變三個函數的順序,但只有 isPrime_V1(n) 被裝飾?)

心原一馬 iT邦研究生 5 級 ‧ 2020-07-05 10:28:16 檢舉

嗨嗨,邦友你好,謝謝你的發問,
這只是我單純忘記加而已,
應該每個函數都要加才會「裝飾」到,
已於文中修正~

我要留言

立即登入留言