程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

python裝飾器

編輯:Python

相信我們或多或少都見過python中的裝飾器,如:

@property
@wrap(func)

下面就講解一下python中裝飾器的作用!

首先記住裝飾器的作用:

在運行函數之前會運行這個函數前的裝飾器,然後起到改變函數的功能的作用!
def my_decorator(my_func):
def wrap_myfunc():
my_func()
print('我睡醒了') #這裡我們改變了原來函數的功能
return wrap_myfunc
def my_func():
print('我在睡覺')
my_func = my_decorator(my_func)
my_func()

比如如上面代碼所示,我有一個函數my_func的功能是打印我在睡覺,但現在我想改變我們函數的功能使我們睡醒,於是我們寫了個my_decorator函數,將我們的my_func函數作為參數傳進去,然後在內層的wrap_myfunc函數中打印我睡醒了!然後我們重新調用my_func()發現結果為:

 可以看見,這麼操作確實改變了my_func()的功能,這其實就是裝飾器的原理,只不過我們並沒有用@關鍵字而是自己手搓了出來。

那麼原理是什麼呢?

my_func = my_decorator(my_func)這一行因為my_decorator函數返回的是wrap_myfunc,所以其實就是my_func=wrap_myfunc,所以可以看出其實是狸貓換太子將我們原來的my_func函數換成wrap_myfunc了!

那麼我們把上面的代碼換成用正式的裝飾器來實現就是:

def my_decorator(my_func):
def wrap_myfunc():
my_func()
print('我睡醒了') #這裡我們改變了原來函數的功能
return wrap_myfunc
@my_decorator #由結果可以看到在我們想要改變的函數前加一個@引導的裝飾器 = 我們上面的傳遞函數返回函數 my_func = my_decorator(my_func)
def my_func():
print('我在睡覺')
my_func()

這裡我們在my_func()函數前面加上@my_decorator就等價於my_func = my_decorator(my_func)這一手狸貓換太子!輸出結果:

值得一提的是,我們這手狸貓換太子直接將函數名與注釋文檔換了,雖然我們調用的還是my_func()函數,但是他的名字其實已經不叫my_func了(沒想到吧,底層的名字才是真正的名字):

def my_decorator(my_func):
def wrap_myfunc():
my_func()
print('我睡醒了') #這裡我們改變了原來函數的功能
return wrap_myfunc
@my_decorator #由結果可以看到在我們想要改變的函數前加一個@引導的裝飾器 = 我們上面的傳遞函數返回函數 my_func = my_decorator(my_func)
def my_func():
print('我在睡覺')
my_func()
print(my_func.__name__) # output: wrap_myfunc,這不是我們想要的,可見直接這麼加裝飾器把我們原來函數的名字和注釋文檔重寫了!

 輸出:

 這顯然不是我們想要的,我們只是想改變一下它的功能而已,並沒有說要改變它的名字啊!

我們可以通過from functools import wraps導入這個包並且在我們要改變函數功能的那個函數前加上@wraps(my_func)這個裝飾器並將我們不想改變名字的函數傳進去就不會發生上面那種情況了!

from functools import wraps
def my_decorator(my_func):
@wraps(my_func) #在我們的裝飾器裡再加上這麼一個裝飾器就好了!
def wrap_myfunc():
my_func()
print('我睡醒了')
return wrap_myfunc
@my_decorator
def my_func():
print('我在睡覺')
my_func()
print(my_func.__name__) # output: my_func

運行結果:

上面只是最簡單的一種情況,因為我們的函數並沒有參數與返回值,下面我們稍微加點難度,讓函數帶上參數與返回值,希望不會被繞暈!

from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Do some math."""
return x + x
result = addition_func(4)

 上面這段代碼的作用是:我們原來的addtion_func函數的功能只是返回原來參數的兩倍,我們加上一個裝飾器使得每次調用這個函數時都會先打印 (函數名) was called。你可能會想我自己在原來的函數裡面加一個print語句自己打印不就行了?想的很好,但是如果當代碼量一大,你有100個函數都需要這樣干,那你就要打印一百次,這就比用裝飾器慢了!這時我們裝飾器的作用就體現出來了!

回到我們上面的代碼:

我們發現與第一個案例比,這個案例主要函數多了返回值與參數。我們來分析一下:

調用裝飾器等價於:addition_func = logit(addition_func),而logit函數返回的是with_logging,所以就相當於addition_func = with_logging!那麼result = addtion_func(4)就相當於result = with_logging(4),而with_logging函數又返回的是func函數且他倆參數是一樣的,所以最後就等價於返回addition_func(4)!所以result = 8!

運行結果:

 

結果與我們猜想的一樣!

下面讓我們再加把火,讓我們看看帶參數的裝飾器!

from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打開logfile,並寫入內容
with open(logfile, 'a') as opened_file:
# 現在將日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# 現在一個叫做 out.log 的文件出現了,裡面的內容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# 現在一個叫做 func2.log 的文件出現了,裡面的內容就是上面的字符串

可以明顯的看到,@logit(logfile='func2.log')這一行的裝飾器帶上了參數!

但是我們發現,竟然又套了一層函數!希望別被繞暈!

還是一樣,我們主要關心三個return。還是一樣,加上裝飾器就相當於myfunc2 = logit(my_func2)

而logit函數返回wrapped_funtion函數,所以就相當於myfunc2 = wrapped_function,所以運行myfunc1()就相當於運行wrapped_function(),雖然wrapped_function函數有返回值為func(),但是我們的my_func1()函數並沒有返回值,所以不需要在運行my_func1()時接收返回值(可以自己將pass改為return 'ok',然後str = myfunc1(),再打印str就可以看到有返回值的情況)!

運行結果已經在注釋裡寫了,好像也不是很繞?這個含參裝飾器的功能就是創建一個自定義名字的日志文件將 (函數名) was recalled寫入日志文件!


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved