裝飾器

在講到python內置函數中的map等函數時講到過函數式編程,map函數將另外一個函數名做爲參數使用,其實在python中的,咱們在聲明函數時,函數名稱會看成儲存函數的符號指向了函數的內存地址,因此函數名還能夠作爲一個普通的數據傳遞給任意變量甚至儲存到列表中,如:python

def sun():

  .....

f = sun 

這時f() 和sun()徹底等價程序員

因此能夠認爲一個單純的函數名稱只表明一個指向一個地址的參數,當這個參數後面加上「()」時,這個函數纔會被執行。編程

咱們在平常的開發過程當中能夠知道,一個函數包裝後的代碼塊,通常都是一個完整的功能,若是如今出現一個狀況,須要增長你某個函數的部分功能,又不但願修改調用方式,若是咱們直接去修改函數,可能會承擔函數被修改壞的風險,那有沒有什麼方法能夠不對函數作直接修改,又不改變調用方式的狀況下爲一個函數增長部分功能呢?答案就是使用裝飾器。函數式編程

爲了方便理解,先不直接講解裝飾器,咱們假設有一個看視頻的網站,只要是打開了網頁就能夠直接觀看視頻,假設其中的邏輯以下:函數

def tv():
    print("僞裝在看視頻")
tv()

後來網站有名了,膨脹了,須要有會員才能觀看視頻,如今又要給加一個驗證登陸的功能,咱們僞裝tv()方法很是複雜,若是直接修改,可能一不當心致使程序崩潰,因此想要在函數外面來增長這個功能,可是在其餘程序中調用tv()函數時,調用方式不變,該如何處理呢?不慌,咱們先寫一個實現登陸功能的函數測試

def login():
    print("僞裝在驗證")

那麼如何在調用tv()時,可以運行login方法呢?網站

答案是咱們能夠將tv做爲一個變量,而後將login傳遞給tv,這樣調用tv()時,執行的就是login函數,而後再將真正的tv()函數在login中執行:spa

def tv():
    print("僞裝在看視頻")
def login():
    print("僞裝在驗證")
    tv()
tv = login
tv()

可是這個時候又有問題了,由於在執行login時,tv已經被替換成了login函數,因此在login函數內的tv()實際上仍是執行的login(),這時候咱們可使用第三個變量來儲存真正的tv函數,就能夠解決這個問題:code

def tv():
    print("僞裝在看視頻")
def login():
    print("僞裝在驗證")
    tv2()
tv2 = tv
tv = login
tv()
-----------------------------

僞裝在驗證
僞裝在看視頻視頻

能夠看到返回的結果正是咱們想要達到的效果,很是的完美對不對?不對!在login函數中咱們寫死了函數名,那麼咱們的login函數,就只能使用一次,做爲一個優秀的程序員,怎麼能寫出複用率這麼低的代碼呢?函數是能夠傳遞參數的,咱們徹底能夠將函數名做爲參數傳遞給login函數,怎麼作呢?若是隻是稍做修改,代碼就是以下:

def tv():
    print("僞裝在看視頻")
def login(func):
    print("僞裝在驗證")
    return func
tv = login(tv)
tv()

咱們將login修改爲了須要一個參數的函數,而後咱們將tv函數做爲參數傳遞過去,而後再執行完login函數後,將tv函數做爲返回值傳遞迴tv,以前的tv=login 是將login的內存地址傳遞給tv,而tv = login(tv)是表示執行login()函數後,將返回值傳遞給tv,因此必需要有返回值,而且tv要能被調用,因此只能返回一個函數給tv,如是就成了上面的代碼,運行後結果也是咱們想要的效果,是否是很完美?仔細想一想login是在什麼時候被執行的,並不是是在調用tv時執行,而是在執行 tv = login(tv) 這一段時就被執行了。那麼如今問題來了,咱們想要在調用tv時才執行login,爲tv賦值時不會執行任何邏輯,咱們要怎麼作呢?沒錯,這個地方纔是真正開始將裝飾器,上面的都是廢話!

想一想看,咱們想要在執行tv = login(tv)時,只是將一個函數傳遞給tv,而不會執行任何邏輯,login函數該怎麼寫?首先,確定是要有一個返回值,而且這個返回值確定是一個函數,如是咱們能夠先這麼寫:

def login(func):
    return funcx

固然上面代碼會是錯的,由於funcx找不到,funcx應該是誰呢?login?不行,由於tv()函數沒有參數,而login函數有參數。tv?不行,那就至關於咱們什麼都沒有作,因此這個funcx只能是一個新函數,咱們用這個函數代替以前的login函數,只有執行funcx時,纔會執行驗證的邏輯,而且執行tv函數。那麼問題又來了,若是funcx是在調用tv()時調用,那麼這個函數內又如何執行真正的tv()呢?總不能寫死吧?那和一開始就沒有區別了,索性python裏函數有一種嵌套寫法,能夠在一個函數內再寫一個函數,因此咱們最終的樣子就是:

def tv():
    print("僞裝在看視頻")

def login(func):
    def funcx():
        print("僞裝在驗證")
        return func()
    return funcx

tv = login(tv)
tv()

這個時候就是真正的 解決了咱們的問題,當tv()去掉時,整個程序,什麼都不會發生,能夠自行測試。

而後,python中的裝飾器就是爲了簡化這種狀況下的代碼:

def login(func):
    def funcx():
        print("僞裝在驗證")
        return func()
    return funcx

@login
def tv():
    print("僞裝在看視頻")

tv()

這種寫法和上面的效果是同樣的。

而後而後,再假設,若是咱們的tv函數須要參數,或者有返回值怎麼辦呢?下面的寫法完美的解決了這兩種狀況

def login(func):
    def funcx(name):
        print("僞裝在驗證")
        return func(name)
    return funcx

@login
def tv(name):
    print(name,"僞裝在看視頻")
    return 1

a = tv("yq")
print("a=",a)

理解起來應該不難,就很少作解釋了,有時候可能會要同時知足幾個函數增長同一個功能的要求,而其餘函數須要的參數又各不相同,這個時候能夠利用可變參數將咱們的裝飾器進行升級。

def login(func):
    def funcx(*args,**kwargs):
        print("僞裝在驗證")
        return func(*args,**kwargs)
    return funcx

這樣就隨你有沒有參數,什麼樣的參數,有沒有返回值了。

這就完了?並無,裝飾器自身還能夠有參數的,由於用的不會很少,因此不作太多的假設,直接上代碼。

def login(level):
    def inner(func):
        def funcx(*args,**kwargs):
            print("僞裝在驗證")
            if level == "VVIP":
                print("歡迎土豪爸爸")
            return func(*args,**kwargs)
        return funcx
    return inner

@login("VIP")
def tv(name):
    print(name,"僞裝在看視頻")

@login("VVIP")
def tv2(name):
    print(name,"僞裝在看視頻")

#tv("yq")
tv2("yq")

這個裏面在裝飾器後面增長的參數,其實就只須要在login函數外面再增長一層,沒錯,你看的是像裏面增長了一層,其實只是名字沒有修改,你能夠看成是在原有的基礎上在外面增長了一層,而外面這層的邏輯很簡單,就是將裝飾器傳來的參數接收一下,而後再調用原來的裝飾器函數便可。

裝飾器的內容就這麼多,能夠說是將函數式編程表現的淋漓盡致,其實重點就在於一個函數的內存地址隨你怎麼傳,只要是加了「()」就變成了執行函數,在沒有"()"以前,就是一個數據而已。弄懂了這個和裝飾器的原理,那再複雜的裝飾器也就只是簡單的邏輯問題了。

相關文章
相關標籤/搜索