內容來源主要爲總結的《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