-
1 # IT老友
-
2 # Python之王
@Author:By Runsen
@Date:2019年07月11日
1.1 裝飾器入門1.2 帶引數的裝飾器2.1 類裝飾器2.2 裝飾器的巢狀3.1 身份認證3.2 日誌記錄3.3 輸入合理性
1、裝飾器
1.1 裝飾器入門
裝飾器,顧名思義,就是用來“裝飾”的。比如@Runsen就是一個裝飾器,其中"Runsen"是你的裝飾器的名字。它能裝飾的東西有:函式、類。
先看兩段程式碼,在這裡my_decorator()就是一個裝飾器,其實裝飾器就是一個函式來的函式
def my_decorator(func):
def wrapper():
print("wrapper of decorator")
func()
return wrapper
def greet():
print("hello world")
greet = my_decorator(greet)
greet()
# 輸出
wrapper of decorator
hello world
my_decorator函式傳入greet函式名方法,中間有一個wrapper內函式方法, return wrapper說明要執行wrapper內函式,wrapper內函式,執行greet函式名方法
greet = my_decorator(greet)這個程式碼可以用裝飾器來替代,在greet上面加一個@my_decorator,直接執行greet()
def my_decorator(func):
def wrapper():
print("wrapper of decorator")
func()
return wrapper
@my_decorator
def greet():
print("hello world")
greet()
wrapper of decorator
hello world
裝飾器就是繼承了 my_decorator函式,因此先呼叫my_decorator中的wrapper打印出 wrapper of decorator,然後func()被呼叫,指的就是 greet(),所以在打印出hello world
1.2 帶引數的裝飾器
有時候函式需要傳入引數,這時候用*args, **kwargs接受就可以了。
def my_decorator(func):
def wrapper(*args, **kwargs):
print("wrapper of decorator")
func(*args, **kwargs)
return wrapper
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
print("wrapper of decorator")
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(4)
def greet(message):
print(message)
greet("hello world")
# 輸出:
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
wrapper of decorator
hello world
但是自定義引數的裝飾器將改變函式本身的元資訊,即函式不再是本身的函式
**greet.__name__
## 輸出
"wrapper"
help(greet)
# 輸出
Help on function wrapper in module __main__:
wrapper(*args, **kwargs)
這時,需要使用內建模組functools.wrap會保留原函式的元信
在原始碼中@functools.wraps也是這麼來的
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("wrapper of decorator")
func(*args, **kwargs)
return wrapper
@my_decorator
def greet(message):
print(message)
greet.__name__
# 輸出
"greet"
2、裝飾器進階
2.1 類裝飾器
類裝飾器主要依賴函式__call__ ,每當呼叫一個類的例項,函式__call__就會執行一次
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print("num of calls is: {}".format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
# 輸出
num of calls is: 1
hello world
example()
# 輸出
num of calls is: 2
hello world
2.2 裝飾器的巢狀
如果一個函式上面有多個裝飾器,叫做裝飾器的巢狀
從下到上
@decorator1
@decorator2
@decorator3
def func():
...
那麼相當於,從裡到外
decorator1(decorator2(decorator3(func)))
import functools
def my_decorator1(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("execute decorator1")
func(*args, **kwargs)
return wrapper
def my_decorator2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("execute decorator2")
func(*args, **kwargs)
return wrapper
@my_decorator1
@my_decorator2
def greet(message):
print(message)
greet("hello world")
# 輸出
execute decorator1
execute decorator2
hello world
3、裝飾器用法例項
3.1 身份認證
import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
request = args[0]
if check_user_logged_in(request): # 如果使用者處於登入狀態
return func(*args, **kwargs) # 執行函式 post_comment()
else:
raise Exception("Authentication failed")
return wrapper
@authenticate
def post_comment(request, ...)
...
3.2 日誌記錄
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print("{} took {} ms".format(func.__name__, (end - start) * 1000))
return res
return wrapper
@log_execution_time
def calculate_similarity(items):
...
3.3 輸入合理性
import functools
def validation_check(input):
@functools.wraps(func)
def wrapper(*args, **kwargs):
... # 檢查輸入是否合法
@validation_check
def neural_network_training(param1, param2, ...):
...
所謂的裝飾器,其實就是透過裝飾器函式,來修改函式的一些功能,使得原函式不需要修改
-
3 # 軟體測試開發技術棧
先分解一下樓主提出的問題:
如何理解return一個函式,它與return一個值得用法區別在哪?在wrapper函式中,為什麼能返回一個在wrapper函式中沒有定義的func函式?在簡單概括一下這兩個問題涉及到的Python 知識點 :
問題1:Python的函式物件,函式可以被賦值,函式可以作為引數傳遞,函式可以作為返回值。
問題2:Python 的 閉包
接下來,我們根據例項,逐一的介紹一下:
函式物件Python一切皆物件,函式這一語法結構也是一個物件。函式被稱為第一類物件,函式可以被當做資料傳遞。在函式物件中,我們像使用一個普通物件一樣使用函式物件,比如更改函式物件的名字,或者將函式物件作為引數進行傳遞。
函式可以被賦值
執行上述程式碼,輸出如下,請留意程式碼中的註釋資訊。
函式可以作為引數傳遞
執行上述程式碼,輸出如下,請留意程式碼中的註釋資訊。
函式可以作為返回值
如上示例中,當函式(不帶括號)作為返回值時,返回的是函式的記憶體地址,程式碼執行順序及結果,如下:
與上面程式碼不同的是,接下來我們嘗試一下讓fun_b返回 return fun(),多了一個括號,程式碼如下:
當 執行 return fun() 時,實際上是先呼叫fun_a函式,再將fun_a的返回結果作為fun_c的返回,執行程式碼,結果如下:
閉包定義:在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體。閉包在執行時可以有多個例項,不同的引用環境和相同的函式組合可以產生不同的例項。
結合上面例子,一個閉包可以簡單理解為呼叫了一個函式fun_a,這個函式fun_a返回了一個函式fun_b。這個返回的函式fun_b就叫做閉包。在呼叫函式fun_a的時候傳遞的引數a、c就是自由變數。
上面例子中,函式 fun_b 與環境變數 a,c 構成閉包。在建立閉包的時候,我們透過fun_a 的引數 a,c明確這兩個環境變數的取值,因此確定了函式的最終形式(y = 2b + 10)。我們只需要變換引數a,b就可以獲得不同的直線表達函式。由此,我們可以看到,閉包的引入提高程式碼了程式碼的可複用性,更加簡潔。執行程式碼,輸出結果如下:
修飾器顧名思義,從字面意思可以理解為,它是用來"裝飾"Python的工具,使得程式碼更具有Python簡潔的風格。裝飾器本質上是Python函式,能夠實現讓其他函式在不需要做任何程式碼變動的前提下增加額外功能。
可以看出,fun_a(fun_b)的執行過程如下:
執行函式fun_a,將fun_b當作引數傳進去,fun_b()本身也是物件。執行print (fun()) 程式碼時,先執行了 fun_b(),然後列印"Run Function B" , 返回 2019-06-11 21:17:27 。print(fun()) 列印了fun_b()的返回結果 2019-06-11 21:17:27 。使用修飾器進行改造,如下:
執行fun_b相當於 fun_b = fun_a(fun_b) ,只是在定義fun_b時,在其前使用@fun_a 進行修飾。
再引入閉包進行改造,如下:
在fun_a內部的函式retry(),是如何獲取fun這個引數來執行的?執行fun_a函式return的是retry這個函式,而retry並沒有接受fun這個傳參。這就是Python裡的閉包的概念,閉包就是指執行時自帶上下文(自由變數)的函式,如這裡的retry函式,他執行的時候自帶了上層函式fun_a傳給他的fun這個引數,所以才可以在執行時對fun進行處理和輸出。
修飾器實現重試機制簡單的重試機制實現
複雜的重試機制實現支援重試次數和等待時間,如下:
進一步深入瞭解修飾器,可以閱讀這篇文章:
https://www.toutiao.com/i6731320536732795405/
回覆列表
長文預警,【最淺顯易懂的裝飾器講解】
能不能專業地複製題目?配上程式碼,問題分段。
我來給提主配上問題的程式碼。
正式回答:
1:如何理解return一個函式,它與return一個值得用法區別在哪?
敲黑板,"python中,一切都是物件"。
值是物件,函式也是物件。
上圖,num是int類的例項物件,funcobj是function類的一個例項物件。
所以返回一個值和返回一個函式並沒有什麼不同,本質都是返回一個物件。
但是由於值型別和函式型別的使用方法不同,值直接使用,函式需要加上()呼叫。
2.在wrapper函式中,為什麼能返回一個在wrapper函式中沒有定義的func函式?
先更正你的提問,wrapper函式並沒有返回func函式,而是返回func函式的執行結果。
因此,作為引數傳遞給wrapper函式之後,wrapper當然可以呼叫func函式。
3.怎麼理解在log中作為引數存在的func,在wrapper函式中成了函式?
相信你已經明白用物件的眼光看待,因此和問題2其實是一個問題。
4.這對log函式本身的使用有哪些影響,或者說當A函式的引數是一個函式時,如何使用A函式?
什麼是裝飾器?裝飾器就是裝飾函式的!
問題圖中的log函式就是為了在不更改func的情況下,每次呼叫func之前,都會執行
想到了什麼?日誌!沒錯!
那麼,你可能會問,為什麼不在func函式print日誌呢?
問得實在太好了!
1:如果func函式是你寫的,那麼你當然可以這麼做;如果不是你寫的,你這麼做試試?
比如在系統open函式的最前面加上print()....
2:如果你有n個函式,在執行的前後都會執行一些類似的程式碼。
以下是2個不同的寫法
顯然右邊程式碼量更少,更容易維護,但是還有更好的寫法。
請注意,不修改add函式和sub函式的情況下,就為這2個不同的函式的執行前後增加了新的功能。
把add函式和sub函式裝飾得更強大了。
上述程式碼後半段仍有改進的空間。
看,經過@decorator裝飾add和sub函式之後,使用時更方便了。
細心的朋友,相信已經注意到了add函式和sub函式的引數不一樣的。
沒有錯,我是故意的。
奧妙在於*args 和**kwgs,可變引數。
上圖是對指定引數、可變引數*args、可變**kwgs的示例。
對於裝飾器來說,不需要指定引數,因此只需*args和**kwgs即可以表示。