1. 程式人生 > >Python核心程式設計 | 裝飾器

Python核心程式設計 | 裝飾器

    裝飾器是程式開發的基礎知識,用好裝飾器,在程式開發中能夠提高效率 它可以在不需要修改每個函式內部程式碼的情況下,為多個函式新增附加功能,如許可權驗證,log日誌等   涉及點:   1.先梳理一下  
>>> def fun():
    print('Fun...')
# fun 是函式
# fun() 是執行函式
 
#A
>>> def test():
    print(1)
>>> def test():
    print(2)
#B
>>> test()
2
>>>

 

  重複定義函式不會報錯 只是會讓函式名指向不同的函式體  
>>> def test():
    print('TEST')
>>> def fun():
    print('FUN')
>>> test=fun # test改變指向,指向了和fun一樣的函式體
>>> test()
FUN
>>>

 

  在C和C++中,會告訴你兩個函式重名了,在Python中不會,會預設改變指向   2.開放封閉原則 開放:對擴充套件開發 封閉:已實現的功能程式碼塊 1.新增新需求,最好不要改動原來的程式碼塊,因為別處已經呼叫的地方可能會報錯, 儘可能進行擴充套件 2.開發的時候,別輕易刪除程式碼   3.現有fun1,fun2函式,在每次呼叫這兩個函式時,需要進行許可權驗證
 
>>> def fun1():
    print('fun1')
 
>>> def fun2():
    print('fun2')
 
>>> def test(fun):
    def inner():
      print('驗證')
      fun()
    return inner
 
>>> mytest=test(fun1)
>>> mytest()
驗證
fun1
>>>

 

不是直接在每個需要加驗證的函式裡面修改,那樣違反了 開放封閉 原則 而是建立一個閉包: test的引數在內部函式中被呼叫,被呼叫時,先執行驗證功能。傳入的引數決定了是哪個函式 所以即可以傳入fun1,也可以傳入fun2   上面的程式碼雖然添加了驗證功能,在不改變原有程式碼部分的情況下。 但每次呼叫不是直接呼叫原來的fun1 、fun2;而是呼叫另一個函式 現在改變一下程式碼:
>>> def
fun1():     print('fun1') >>> def fun2():     print('fun2') >>> def test(fun):     def inner():       print('驗證功能')       fun()     return inner

 

>>> fun1=test(fun1) # 現在能夠通過原有函式名直接呼叫,不改變原有函式程式碼 >>> fun1() 驗證功能 fun1 >>> 如何解釋上述程式碼? 其實只是改變了函式名對函式體的引用 執行fun1=test(fun1)時,test中的fun引數指向了fun1的函式體。inner中,執行了fun:fun()。現在讓fun1改變它的指向,讓它指向inner 所以呼叫fun1就是呼叫inner函式 簡言之:通過改變引用,讓inner函式裡面既包含新功能,也包含了舊功能 然後改變原來函式名的指向,讓它指向inner函式。 但是,每次都需要這麼寫:fun1=test(fun1) 過於麻煩   4.使用語法糖: 在fun1前面加 @test 等價於 fun1=test(fun1)
>>> def test(fun):
    def inner():
      print('驗證功能')
      fun()
    return inner
>>> @test
  def fun1():
    print('fun1')
>>> fun1()
驗證功能
fun1
>>>
裝飾器:在原有的基礎上,不改動原先程式碼,然後新增新的功能,進行裝飾(Python特有)   5.例子 執行結果:

 

這類程式碼應該怎麼看? 如14~16行程式碼,把test1的功能「給」makeBold裡的fn 把執行test1當成執行makeBold裡的wrapped函式   test3添加了兩個裝飾器,為什麼結果是<b><i>hello world-3</i></b> 6.兩個裝飾器裝飾過程:函式test3為例 裝飾器@makeBold下面是裝飾器@makeItalic,需要等裝飾器@makeItalic將test3進行裝飾之後 返回一個新的引用,再進行裝飾,所以此時裝飾器@makeBold沒有被執行,先執行裝飾器@makeItalic 1. @makeItalic:test3=makeItalic(test3) 此時test3指向新引用,makeItalic中的wrapped [email protected]:test3=makeBold(test3)此時傳入的test3為@makeItalic裝飾後新的test3  

 

  此時,test3指向了@makeBold中的wrapped,@makeBold中的fn指向了@makeItalic中的wrapped,@makeItalic中的fn指向了原來test3的函式體。 倒著裝,順著執行   7.裝飾器什麼時候進行裝飾? 在定義的時候就進行裝飾,不是等到呼叫才裝飾
>>> def fun(fn):
    print('正在裝飾...')
      def inner():
      fn()
    return inner
>>> @fun
def test():
  print('函式')
正在裝飾...
>>>
  上述情況是沒有引數的情況下進行 8.現在看一下有兩個引數的情況

 

因為(原函式)test需要兩個引數,所以在呼叫經過裝飾器裝飾的test時也要傳入兩個引數, 這兩個引數經過inner,fn傳入(原函式)test的函式體   但是,被裝飾的函式形參發生變化時,都需要改變「裝飾器」函式的形參嗎 9.不定引數 使用不定引數,這樣不管被裝飾的函式形參怎麼變化,裝飾器會自動匹配,只需要更改被裝飾的函式的程式碼就可以 如:

 

函式test的形參由2個增加到3個: 函式test的形參由3個改為沒有:   10.被裝飾的函式有返回值的情況 列印結果: None 由於fn()返回的結果沒有被inner返回,所以列印test時為None 修改:   執行fn()時,執行的是第10行test裡面的程式碼,返回了一個字串,這個字串,也就是fn的返回值 在inner被變數rn接收,inner又返回了變數rn。   11.通用裝飾器

 

執行結果: ... None ... test2 ... vaule: 10 ... vaule: 10 20 30 通用的裝飾器,同時滿足了多種函式的情況,有無參、多參、有無返回值等 當函式沒有返回值時,inner函式中的變數ret接收到None inner的形參為 *args、**kwargs為不定參   12.帶引數的裝飾器