Python之裝飾器

內容來源主要爲總結的《head first Python》筆記

Python函數修飾符又稱裝飾器,可以爲現有函數增加代碼而不必修改現有函數代碼。

要編寫一個修飾符,需要了解四個問題:

1. 如何創建一個函數

2. 如何把一個函數作爲參數傳遞到另一個函數

3. 如何從函數返回一個函數

4. 如何處理任意數量和類型的函數參數

創建函數

Python創建函數通過關鍵字def指定函數名,並列出函數可能有的參數(函數的輸入)。還引入return關鍵字可選,用來向調用該函數的代碼傳回一個值。

例如: def add_num(a, b):
              """funtion description here """
              #single line comments here
              return a + b

Python解釋器不需要指定函數參數或返回值的類型。任何對象均可作爲參數發送給函數,並且函數允許將任何對象作爲返回值傳回。Python只檢查是否提供了參數和返回值,從來不會檢查類型。

函數作爲參數傳遞

Python中一切皆對象,函數也是對象,有一個對象ID。分析以下例子。

例:>>> msg='hello world'
       >>> def hello():
                    print(msg)
       >>> id(msg)
       3212332312
       >>> id(hello)
       3212336659
       >>> type(msg)
       <class 'str'>
       >>> type(hello)
       <class 'function'>
       >>> hello()
       hello world

當把hello函數傳遞給id和type, 這裏沒有調用hello, 只是把這個函數名作參數傳遞到這兩個函數。

調用傳入的函數

函數對象作爲參數傳遞到一個函數時,這個函數可以調用所傳入的函數對象。給出一個小示例,函數apply有兩個參數:一個函數對象和一個值。

def apply(func value):
      return func(value)

測試該函數如下:

>>> apply(print, 42)
42
>>> apply(len, 'python')
6
>>> apply(tpye, apply)
<class 'function'>

函數可以嵌套在函數中

Python中,函數代碼組中的代碼可以是任意代碼,包括定義另一個函數的代碼,通常稱爲嵌套或內部函數。同樣可以從外部函數返回嵌套函數(實際返回的是一個函數對象)。

def outer():
       def inner():
              print('This is inner')
       print('This is outer, invoking inner.')
       inner()

函數inner嵌套在outer函數中。出了在outer的代碼組中調用inner, 不能在其他任何地方調用這個函數。innter在outer的局部作用域。
如果一個函數很複雜,包含多行代碼,把一些函數代碼抽取到一個嵌套函數中就會很有意義。而且代碼會更易讀。
更常見的一個用法是:外圍函數使用return語句返回嵌套函數作爲它的返回值修飾符就是採用這種方法來創建

從函數返回一個函數

>>> def outer():
    def inner():
        print('This is inner.')
    print('This is outer, returning inner')
    return inner #不加()只使用函數名,就會得到函數對象
>>> i=outer()
This is outer, returning inner
>>> type(i)
<class 'function'>
>>> i()
This is inner.

從例中可看出, 首先將調用outer的結果賦給名爲i的變量,調用i,會執行innter函數的代碼。

處理任意數量和類型的函數參數

Python使用*接收一個任意的參數表(0個或多個參數)。如下例子:

>>> def myfunc(* args):
    for a in args:
        print(a, end=' ')
    if args:
        print()
>>> myfunc()  #不提供參數
>>> myfunc(10) # 一個參數
10 
>>> myfunc(1, 'two', 3, 'four') #參數可以混合多種類型
1 two 3 four 

Python還可以直接使用*,當向函數提供一個列表作爲參數,這個列表儘管可能包含多個值,會被處理爲一項,即一個列表參數。爲了指示解釋器展開這個列表,把每個列表項作爲一個單獨的參數,調用函數時,需要在列表名前面加上*作爲前綴。

>>> values=[1, 2, 3,4, 5, 6,7]
>>> myfunc(values)
[1, 2, 3, 4, 5, 6, 7] 
>>> myfunc(*values)
1 2 3 4 5 6 7 

Python使用**接收任意多個關鍵字參數,即參數字典。

>>> def myfunc2(**kwargs):
    for k, v in kwargs.items():
        print(k, v, sep='->', end=' ')
    if kwargs:
        print()
>>> myfunc2(a=10, b=20)
a->10 b->20 
>>> myfunc2()

也可直接使用**。 

>>> values={'a':1, 'b':2, 'c':'python'}
>>> myfunc2(**values)
a->1 b->2 c->python 

當然, * 和**可以同時使用。

>>> def myfunc3(*args, ** kwargs):
    if args:
        for a in args:
            print(a, end=' ')
        print()
    if kwargs:
        for k, v in kwargs.items():
            print(k, v, sep='->', end=' ')
        print()

>>> myfunc3()
>>> myfunc3(1,2,3)
1 2 3 
>>> myfunc3(a=10, b=20)
a->10 b->20 
>>> myfunc3(1,2,3,a=10, b=20)
1 2 3 
a->10 b->20 

創建函數修飾符

要創建一個函數修飾符需要知道:
1. 修飾符是一個函數
2. 修飾符取被修飾函數作爲參數
3. 修飾符返回一個新函數
4. 修飾符維護被修飾函數的簽名
    修飾符需要確保它返回的函數與被修飾函數有相同的參數(個數和類型都相同)。函數參數的個數和類型稱爲其簽名。

舉例,web相關的很多函數的前提是需要確保頁面已登陸。所以需要創建判斷是否登陸的修飾符。

創建一個函數如下:
def check_logged_in(fun):  #有一個參數,即被修飾函數的函數對象
       def wrapper():              #定義嵌套函數,名爲wrapper
             if 'login_in' in session:
                   return fun()       #調用被修飾的函數,注意此處使用了(),代表函數調用
             return 'you are not logged in.'
      return wrapper              #返回嵌套函數的函數對象

 前面第四條提到,必須要確保返回的函數與被修飾的函數有同樣的參數,一個修飾符應用到現有函數時,對這個現有函數的所有調用都會替換爲調用修飾符返回的函數。所以,如果現有函數接收多少個參數,包裝函數也必須對應接收同樣多個參數。只需使wrapper支持任意數量和類型的參數即可。

def check_logged_in(fun):  
       def wrapper(*args, **kwargs):              #接收任意數量和類型的參數
             if 'login_in' in session:
                   return fun(*args, **kwargs)       
             return 'you are not logged in.'
      return wrapper 

functools模塊的wraps函數

最後一個問題是函數如何向解釋器標識自己的身份。Python標準庫提供了一個模塊來處理這些。只需導入模塊functools, 然後調用wraps函數即可。

wraps函數實現爲一個修飾符,所以實際上並不是調用這個函數,而是在自己創建的修飾符中修飾wrapper函數。

from functools import wraps
def check_logged_in(fun):  
       @wraps(func) 
                             #一定要傳入func作爲參數
       def wrapper(*args, **kwargs):      #接收任意數量和類型的參數
             if 'login_in' in session:
                   return fun(*args, **kwargs)       
             return 'you are not logged in.'
      return wrapper 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章