您好,登錄后才能下訂單哦!
這篇文章主要介紹“Python中裝飾器的基本功能有哪些”,在日常操作中,相信很多人在Python中裝飾器的基本功能有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Python中裝飾器的基本功能有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在 python 中,裝飾器由于是 python 語言自帶的一個功能,因此,對于其實現以及其用法就會感到比較奇怪,這里我記錄一下對它的理解,加深自己的印象。
對于什么是裝飾器,我們其實應該知道為什么會存在裝飾器。
裝飾器是 python 引入的一個非常有意思的功能,它主要用于解決想要在原有函數或類的基礎上進行功能擴展,但又不會破壞這個函數本身的功能。并且我們可以方便的添加和去除這一部分擴展的功能的需求。
例如:當你在調試代碼的時候,你想要給一些功能函數添加打印調試信息。但是,這個功能只在我們調試的時候使用,正式發布的時候是不需要的。如果此時,你在函數內部修改,由于有多個函數都需要添加相同的或類似的信息,那么,你就需要逐一修改,這個就有點麻煩了,此時我們就可以使用裝飾器來解決這一問題。
在解釋如何使用裝飾器之前,我們需要先把和裝飾器有關的基本概念給講一下。
在 python 中,一切皆是對象,也就是說,我們定義的變量和函數都是一個對象。而是對象就意味著我們可以獲得這個對象的屬性,例如函數對象有一個 __name__ 的屬性:
def function(): #定義一個函數 print('this is a function !') function() print(function) #打印函數名的地址 print(function.__name__) #打印函數名 a = function #把函數賦給一個變量 a() print(a) #打印 a 的地址 print(a.__name__) #再次打印函數名
打印結果:
this is a function !
<function function at 0x0000029F83C17F70>
function
this is a function !
<function function at 0x0000029F83C17F70>
function
由打印可以看出,我們的函數名在賦給另一個變量的時候,其函數地址和函數屬性中的函數名是沒有變化的。也就是說,當我們在定義函數的時候,我們的函數名和普通的變量是一樣的,唯一的不同就是,我們的函數名會指向一個內存空間,而這片空間內保存的是一個函數體的內容。
但是,當我們把這個函數名賦值給其他變量的時候,這個函數名就會把它執行的內存空間的地址賦值給另一個變量,因此,另一個變量也就成為了一個函數了。
這里我們已經能夠注意到了,函數名如果不加 () 那么它和普通的變量一樣,而加了 () 之后,它就會去執行我們的函數內容。
這里我們把試著刪除我們定義時候使用的函數名:
del function #刪除 function 函數 a() #執行 a() print(a) #打印出 a 指向的地址 print(a.__name__) #打印a的函數名 function() print(function) print(function.__name__)
查看打印:
this is a function !
<function function at 0x000002258DC17F70>
function
NameError: name 'function' is not defined
可以看到,我們的 function() 函數名提示沒有定義,但是我們的 a() 函數卻可以正常的打印出來。這里的 del 其實就是把我們的 function 這個函數名的指針給指向的函數地址給刪去了,此時它變成立一個真正的未定義的變量了。
既然函數名和普通的變量可以相互賦值,那就說明,我們也可以像使用普通變量一樣使用函數名了。
在函數中定義函數:
我們可以像定義普通變量一樣,在一個函數中定義另一個函數:
def function1(): print('this is function 1') def function2(): print('this is function 2 !') return 0 function2() return 0 function1() function2()
打印如下:
this is function 1
this is function 2 !
NameError: name 'function2' is not defined
可以看到,我們在 function1 中定義了一個 function2 函數,而且在 function1 中使用了 function2 這個函數。但是,當我們在外面使用 function2 這個函數的時候,卻打印了該函數未定義。這里說明,函數內定義的函數的作用域也僅限于函數內部,和我們的局部變量是一樣的。
但是,當我們把函數作為返回值的時候,這個情況就不一樣了,這里參考我上一篇文章:Python中的閉包中的變量作用域問題
在函數中返回函數名:
既然我們可以在一個函數中定義另一個函數,那么也就可以在函數中返回另一個函數:
def function1(): print('this is function 1') def function2(): print('this is function 2 !') return 0 function2() #在函數內部使用該函數 return function2 #返回該函數的函數名 a = function1() #把函數名返回給一個變量 a()
打印如下:
this is function 1
this is function 2 !
this is function 2 !
這里可以看到,我們的這個在函數 function1 中定義并返回了函數 function2 并在外部使用一個變量來接收 funciton1 的返回值。由此可以看出,函數名和變量的使用方式差別不大。
注意: 雖然我們說的時候會說在一個函數中返回另一個函數,但是,實際上,我們返回的只是這個函數的函數名(不帶括號'()‘)。
把函數名作為參數使用:
def hello(): print("hello") def function1(func): #接收一個參數 print('before call hello !') func() print('after call hello !') #function2() #在函數內部使用該函數 function1(hello) #把 hello 作為參數傳遞進去
打印如下:
before call hello !
hello
after call hello !
由打印可以知道,我們在函數 function1 中定義的接收參數 func 我們在定義的時候并沒有采用什么特殊的方式,而是和普通參數一樣定義。之后,在外部調用 function1 的使用,把函數名 hello 當作參數傳遞進去了。隨后,我們運行 function1 并在 function1 中成功調用了 hello 函數。
現在,讓我們再重新看一下什么是裝飾器,我們在上面的把函數名作為參數使用時,已經實現了一個和裝飾器功能類似的函數了。假如我們的 hello() 函數是我們的功能函數,而 function1 作為我們的裝飾器,那么,我們成功實現了在不改變 hello() 函數的基礎上,通過把它作為參數使用而增加了其他的打印內容。
雖然我們上面實現了一個類似裝飾器的功能,但是,我們可以看到,使用這個的時候我們需要每次都給 function1 傳入一個函數,這樣使用就很麻煩了。下面我們改造一下這個函數:
def decorator(func): #裝飾器函數,用于接收一個函數參數 def wrapper(): #定義一個內函數 wrapper print('before call hello !') func() print('after call hello !') return wrapper #把內函數做未返回值 def hello(): print("hello") hello = decorator(hello) #重新定義一個函數 hello1 hello() #執行 hello
打印如下:
before call hello !
hello
after call hello !
通過上面的打印可以看到,我們新更改的這個函數可以實現和上面的函數一樣的功能。但是,我們這里在使用它的時候比之前要簡單一些,因為我們可以直接使用舊的函數名來使用新的功能 (這里我們相當于給函數名 hello 賦值了一個新的函數wrapper),當我們想要使用舊函數時,只需要把 hello=function(hello) 這行內容給注釋掉就可以了。
簡單裝飾器
上面的我們已經實現了一個裝飾器的功能,下面我們使用 Python 中自帶的裝飾器來測試一下:
def decorator(func): #裝飾器函數,用于接收一個函數參數 def wrapper(): #定義一個內函數 wrapper print('before call hello !') func() print('after call hello !') return wrapper #把內函數做為返回值 @decorator # '@' 是系統自帶的裝飾器語法糖 def hello(): print('hello') hello()
打印如下:
before call hello !
hello
after call hello !
可以看到,我們使用系統自帶得裝飾器語法實現了和我們上面得函數一樣得功能。
這里我們可以看到,他們兩個唯一得不同就是使用了 @decorator這個符號來代替了 hello=decorator(hello) 這個賦值語句。
到這里,其實我們基本上就已經明白了python所謂得裝飾器的原理和實際用法了,但是,這里我們還有一個問題,那就是這種方法會改變我們的函數的屬性嗎?
我們測試一下:
print(hello.__name__)
打印如下:
wrapper
很明顯,我們的原函數的屬性中的函數名被更改了,其實通過上面自己的實現,我們可以發現,我們使用裝飾器語法其實就是新建了一個函數名,然后用它去接收裝飾器函數的返回函數名,這樣,該該函數肯定還是繼承了裝飾器返回函數的函數名了。
為了解決這個問題,我們可以使用如下方法:
import functools def function(func): #接收一個參數 @functools.wraps(func) def wrapper(): #定義一個內函數 wrapper print('before call hello !') func() print('after call hello !') return wrapper #把內函數做為返回值 @function # '@' 是系統自帶的裝飾器語法糖 def hello(): print('hello') hello() print(hello.__name__)
打印如下:
before call hello !
hello
after call hello !
hello
通過我們使用系統模塊,我們解決了這一問題。
帶參數裝飾器:
上面我們展示了裝飾器的基礎用法,但是,我們可以發現一個問題,那就是這個裝飾器只能用于打印一類的基本操作,有時我們需要在裝飾器函數內傳參,且需要在多個函數中使用同一個裝飾器函數,如果單純使用上面的方法就不太容易操作了。
下面我們展示一種給裝飾器傳參的操作方法:
import functools def logging(level):#裝飾器接收參數函數 def decorator(func): #裝飾器函數,用于接收一個函數 @functools.wraps(func) def wrapper(*args, **kwargs): #定義一個內函數 wrapper if level == 'warn': print('warn: before call %s !' %func.__name__) func() print('warn: after call %s !' %func.__name__) if level == 'error': print('error: before call %s !' %func.__name__) func() print('error: after call %s !' %func.__name__) return wrapper #把內函數做為返回值 return decorator @logging(level='warn') # '@' 是系統自帶的裝飾器語法糖 def hello(): print('hello') @logging(level='error') def function1(): print('function1') hello() function1() print(hello.__name__) print(function1.__name__)
打印如下:
warn: before call hello !
hello
warn: after call hello !
error: before call function1 !
function1
error: after call function1 !
hello
function1
可以看到,我們在兩個函數中使用了一個裝飾器語法,而且給這個裝飾器分別傳了不同的參數,這個才比較符號我們實際可能會用到的情況。
這里第一次看可能感覺有點復雜,而且我們在這里也使用了多層函數嵌套,每層都傳不同的參數。這里我來仔細拆分一下這個函數:
首先我們知道:
@logging def hello(): print('hello') #等價于 logging(hello)
因此:
@logging(level='warn') def hello(): print('hello') #等價于 logging(hello)(level='warn')
下面我們繼續拆解 logging(hello)(level=‘warn') 這句話:
logging(hello)(level='warn')
由于
logging(hello) 返回 decorator
于是
logging(hello)(level='warn')
等價于
decorator(level='warn')
而
decorator 返回 wrapper
因此
這里其實就到了我們最上面的簡單裝飾器了
到這里我們就明白了我們的裝飾器傳參是怎么回事了。
裝飾器類:
由于我們在 python 中會經常使用類來對某一功能進行封裝,這樣,當我們在使用某一功能的時候就更加靈活且方便了。
因此,我們的 python 也給我們提供了實現裝飾器類的使用方法:
class Logging(object): def __init__(self, func): self._func = func def __call__(self): print('class: before call %s !' %self._func.__name__) self._func() print('class: after call %s !' %self._func.__name__) @Logging def hello(): print('Hello') hello()
打印如下:
class: before call hello !
Hello
class: after call hello !
可以看到,我們的類裝飾器的用法和函數類似,只是在定義裝飾器函數的時候,把函數的實現變成了類方法的實現方式。
除了這種最基本的的使用方式,我們其實也可以給類裝飾器傳參:
class Logging(object): def __init__(self, level='INFO'): self._level = level def __call__(self, func): def wrapper(*args, **kwargs): if self._level == 'WARN': print('class: warn before call %s !' %func.__name__) func() print('class: warn after call %s !' %func.__name__) return wrapper @Logging(level='WARN') def hello(): print('Hello') hello()
打印如下:
class: warn before call hello !
Hello
class: warn after call hello !
這里傳參方式和上面直接在類中的 __call__ 中定義函數有些不一樣,這里需要記住兩點:
__init__:不再接收被裝飾函數,而是接收傳入參數;
__call__:接收被裝飾函數,實現裝飾邏輯
這里就不對這個類方法進行深入解析了。
到此,關于“Python中裝飾器的基本功能有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。