Python 裝飾器(Decorator)可以在不改變函數代碼和引用方式的情況下給函數增加新的功能。
提起Python 裝飾器,大家必定感覺特別神祕。其實裝飾器本質上就是一個函數(func_A),它接受被裝飾的函數(func_B)作爲參數,並在其內部嵌套了一個函數(func_C),func_C 對被裝飾的函數B進行封裝(通常是在func B的前後增加一些功能),然後返回這個包裝過的函數func_C。如下所示,我們看到的裝飾器通常是這樣的:
from functools import wraps
def func_A(func):
@wraps(func)
def func_C(*args,**kwargs):
print 'do something before decoration'
func(*args,**kwargs)
print 'do something after decoration'
return func_C
@func_A
def func_B():
pass
那麼這個代碼裏的@wrap和@func_A是什麼意思呢,它們是怎麼來的呢?接下來我將一步步地帶大家理解。下文部分內容參考自https://www.runoob.com/w3cnote/python-func-decorators.html。
1. Python中一切皆對象
在Python中,函數和類也是對象。具體體現在以下幾個方面:
(1)可以賦值給一個變量;
(2)可以添加到集合對象中;
(3)可以作爲參數傳遞給函數;
(4)可以當做函數返回值
# -*-coding:utf-8-*-
def hello(name='world'):
return 'Hello, '+name
print hello() # 調用該函數
# output: Hello, world
'''將函數賦值給變量'''
greet=hello # 可將函數賦值給一個變量(注意這裏沒有加小括號)
print greet() # 運行一下試試
# output: Hello, world
'''將函數添加到集合對象中'''
a_list=[] # 定義一個集合對象
a_list.append(hello) # 將函數添加到集合中
for item in a_list:
print item
# output: <function hello at 0x000000000361E9E8>
'''作爲參數傳遞給函數'''
def print_type(var): # 定義一個帶參數的函數
print type(var)
print_type(hello)
# output: <type 'function'>
'''當做函數返回值'''
def return_a_func():
print '返回一個函數:'
return hello
my_hello=return_a_func()
print my_hello
# output: 返回一個函數:
# <function hello at 0x000000000331E9E8>
2. 在函數中再定義一個函數
剛纔是函數的基本知識了。更進一步的,在Python中我們可以在一個函數中定義另一個函數:
def hi(name="world"):
print "now you are inside the hi() function"
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
print greet()
print welcome()
print "now you are back in the hi() function"
hi()
#output:now you are inside the hi() function
# now you are in the greet() function
# now you are in the welcome() function
# now you are back in the hi() function
# 上面展示了無論何時你調用hi(), greet()和welcome()將會同時被調用。
# 然後greet()和welcome()函數在hi()函數之外是不能訪問的,比如:
greet()
#outputs: NameError: name 'greet' is not defined
那現在我們知道了可以在函數中定義另外的函數。也就是說:我們可以創建嵌套的函數。現在需要再多學一點,就是函數也能返回函數。
3. 從函數中返回函數
其實並不需要再一個函數裏去執行另一個函數,我們也可以將其作爲輸出返回出來:
def hi(name="world"):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == "world":
return greet
else:
return welcome
a = hi()
print a
#outputs: <function greet at 0x7f2143c01500>
#上面清晰地展示了`a`現在指向到hi()函數中的greet()函數
#現在試試這個
print a()
#outputs: now you are in the greet() function
再次看看這個代碼。在 if/else 語句中我們返回 greet 和 welcome,而不是 greet() 和 welcome()。爲什麼那樣?這是因爲當你把一對小括號放在後面,這個函數就會執行;然而如果你不放括號在它後面,那它可以被到處傳遞,並且可以賦值給別的變量而不去執行它。 你明白了嗎?讓我再稍微多解釋點細節。
當我們寫下 a = hi(),hi() 會被執行,而由於 name 參數默認是 world,所以函數 greet 被返回了。如果我們把語句改爲 a = hi(name = "ali"),那麼 welcome 函數將被返回。我們還可以打印出 hi()(),這會輸出 now you are in the greet() function。
4. 將函數作爲參數傳給另一個函數
def hi():
return "hi world!"
def doSomethingBeforeHi(func):
print "I am doing some boring work before executing hi()"
print func()
doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
# hi world!
現在你已經具備所有必需知識,該進一步學習裝飾器是什麼了。裝飾器讓你在一個函數的前後去執行代碼。
5. 你的第一個裝飾器
在上一個例子裏,其實我們已經創建了一個裝飾器!現在我們修改下上面的裝飾器,並編寫一個稍微更有用點的程序:
def a_new_decorator(a_func):
def wrapTheFunction():
print "I am doing some boring work before executing a_func()"
a_func()
print "I am doing some boring work after executing a_func()"
return wrapTheFunction
def a_function_requiring_decoration():
print "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
你看明白了嗎?我們剛剛應用了之前學習到的原理。這正是Python中裝飾器做的事情!它們封裝一個函數,並且用這樣或者那樣的方式來修改它的行爲。現在你也許疑惑,我們在代碼裏並沒有使用@符號?那只是一個簡短的方式來生成一個被裝飾的函數。這裏是我們如何使用@來運行之前的代碼:
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
希望你現在對Python裝飾器的工作原理有一個基本的理解。如果我們運行如下代碼會存在一個問題:
print a_function_requiring_decoration.__name__
# Output: wrapTheFunction
這並不是我們想要的!Ouput輸出應該是"a_function_requiring_decoration"。這裏的函數被warpTheFunction替代了。它重寫了我們函數的名字和註釋文檔(docstring)。幸運的是Python提供給我們一個簡單的函數來解決這個問題,那就是functools.wraps。我們使用functools.wraps修改下上一個例子:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print "I am doing some boring work before executing a_func()"
a_func()
print "I am doing some boring work after executing a_func()"
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey yo! Decorate me!"""
print "I am the function which needs some decoration to remove my foul smell"
print a_function_requiring_decoration.__name__
# Output: a_function_requiring_decoration
爲了將被裝飾函數的參數傳遞給裝飾器,加入了*args和**kwargs。藍本規範:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print func()
# Output: Function is running
can_run = False
print func()
# Output: Function will not run
注意:@wraps接受一個函數來進行裝飾,並加入了複製函數名稱、註釋文檔、參數列表等等的功能。這可以讓我們在裝飾器裏面訪問在裝飾之前的函數的屬性。
以上,就是Python裝飾器的來源。希望能夠對大家有所啓發。