Python中的各種裝飾器解析
前言
Python裝飾器可以在不改變函數(shù)原實(shí)現(xiàn)方式的前提下,為函數(shù)添加額外的功能。裝飾器的功能并不費(fèi)解(func = decorator(func) ),但具體實(shí)現(xiàn)時(shí)有一些細(xì)節(jié)還是需要搞明白。
一、函數(shù)裝飾器
為表述方便,下面例子decorator開頭的表示裝飾器函數(shù),func表示被裝飾函數(shù)。
decorator不帶參數(shù)
#不帶參數(shù)的裝飾器 def decorator_without_args(func): def wrapper(msg): print('I got a decorator') return func(msg) return wrapper @decorator_without_args def func(msg): print(f'msg is : {msg}') func('hello world')
輸出如下:
I got a decorator
msg is : hello world
上面就是最簡(jiǎn)單的裝飾器,值得注意的是:
- 不帶參的decorator裝飾函數(shù)時(shí),勿帶括號(hào)@decorator_without_args
- 裝飾器外層函數(shù)的return不加括號(hào),如return wrapper
- func本身傳給外層的函數(shù)decorator_without_args(),而func的參數(shù)msg則傳給內(nèi)層函數(shù)wrapper()
- func本身def func(msg),與def wrapper(msg)以及wrapper中return func(msg)的參數(shù)個(gè)數(shù)必須保持一致,不一致就會(huì)報(bào)錯(cuò)。這個(gè)特性可以理解為裝飾器的內(nèi)層函數(shù)(wrapper)有兩個(gè)使命:首先是添加額外的功能,其次是將被裝飾函數(shù)(func)的參數(shù)msg接收過來,以便最后rerun func(msg)。而第二點(diǎn)其實(shí)和裝飾器本身想實(shí)現(xiàn)的功能是無關(guān)的,所以為了有更好的通用性,我們可以將裝飾器改造為以下形式,這也是日常所見到的最普遍的形式:
def decorator_without_args(func): def wrapper(*args, **kwargs): print('I got a decorator') return func(*args, **kwargs) return wrapper
decorator帶參數(shù)
實(shí)際項(xiàng)目中,除了func本身需要傳參外,有時(shí)候decorator也需要傳入?yún)?shù),此時(shí)寫法略有不同:
#帶參數(shù)的裝飾器 def decorator_with_args(flag): def decorator(func): def wrapper(*agrs, **kwargs): if flag: print('flag is True') else: print('flag is False') return func(*agrs, **kwargs) return wrapper return decorator @decorator_with_args(True) def func(msg): print(f'msg is : {msg}') func('hello world')
值得注意的是:
- 以上示例相當(dāng)于func = decorator_with_args(flag)(func)
- 帶參的decorator裝飾函數(shù)時(shí),請(qǐng)加上括號(hào)并傳入需要的參數(shù) @decorator_with_args(True)
- 一共有三層,最外層用于接收裝飾器函數(shù)的參數(shù),內(nèi)層和不帶參的裝飾器一樣。因此可以得出結(jié)論:若裝飾器函數(shù)帶參,需要寫成三層,最外層用于接收裝飾器函數(shù)的參數(shù),中間一層接受被裝飾函數(shù)func,最里面一層用于接收被裝飾函數(shù)func的參數(shù)(*args, **kwargs)
二、類的裝飾器
表示在類上使用的裝飾器,類是被裝飾對(duì)象,請(qǐng)區(qū)分“類裝飾器”,舉個(gè)最經(jīng)典的例子—裝飾器實(shí)現(xiàn)單例模式:
# 類的裝飾器 def decorator_singleton(cls): def wrapper(*args, **kwargs): if not hasattr(cls, '_instance'): print('I am created firstly.') cls._instance = cls(*args, **kwargs) else: print('I have been created,so I am just an old instance!!!') return cls._instance return wrapper @decorator_singleton class Register: pass a = Register() b = Register() id_a = id(a) id_b = id(b) print(f'a id is:{id_a} \nb id is:{id_b}')
輸出如下:
I am created firstly.
I have been created,so I am just an old instance!!!
a id is:140077188368368
b id is:140077188368368
可以看出來,單例裝飾器起到了作用,執(zhí)行過程可以這么表示:
Register = decorator_singleton(Register)
相當(dāng)于將類本身作為參數(shù)傳給裝飾器,最后裝飾器再返回一個(gè)實(shí)例對(duì)象。事實(shí)上,把Register視作一個(gè)函數(shù)就很好理解了,畢竟Register實(shí)例化時(shí),也要執(zhí)行Register()。
三、類裝飾器
表示類作為裝飾器,此時(shí)裝飾器是用類實(shí)現(xiàn)的,與“類的裝飾器”做區(qū)分。
# 類裝飾器 class Decorator: def __init__(self, func): print('I am initialing') self.func = func def __call__(self, *args, **kwargs): print('I am called') return self.func(*args, **kwargs) @Decorator def func(msg): print(f'msg is {msg}') func('hello world')
輸出:
I am initialing
I am called
msg is hello world
如果明白了裝飾器的原理,類裝飾器也很好理解。事實(shí)上,任何類型裝飾器,要起到作用,有兩個(gè)隱式前提:
- 裝飾器本身是一個(gè)可調(diào)用對(duì)象(callable),畢竟本質(zhì)上繞不開func = decorator(func)
- 必須向裝飾器傳入兩類參數(shù):一是func本身,而是func的參數(shù)(*args, **kwargs)
在Python中,對(duì)于類對(duì)象class,只要實(shí)現(xiàn)了__call__()方法,該類實(shí)例化后就可以像調(diào)用函數(shù)一樣使用這個(gè)實(shí)例。上面示例中,類Decorator實(shí)例化后可以像函數(shù)一樣被調(diào)用,這就滿足了裝飾器第一個(gè)前提。 對(duì)于第二個(gè)前提,眾所周知,類的初始化變量可以通過__init__()接收,因此,通過init將func傳入類中,而func中參數(shù)則被傳到了__call__()中。類的實(shí)例化過程想必大家也都知道,總是先執(zhí)行init(),因此I am initialing最先被打印出來。此處小伙伴可能有疑問了,可不可以將func和func中參數(shù)全都傳給__call__(),從而省去__init__()呢?或者說,可不可以將func和func中的參數(shù)全都傳給__init()__而省去__call__()呢?答案是不可以。事實(shí)上,裝飾的過程分為兩步:
func = decorator(func) func(args)
兩類參數(shù)(被裝飾函數(shù)本身,及其參數(shù))也是分兩次傳入裝飾器的。對(duì)于類裝飾器,第一步其實(shí)是實(shí)例化類裝飾器,所以func只能傳給__init__()。而func的args則必須傳給__call__(),至于為什么,請(qǐng)看下面結(jié)論。此處有沒有似曾相識(shí)的感覺?其實(shí)跟函數(shù)裝飾器func要傳給外層函數(shù),args傳給內(nèi)層函數(shù)一個(gè)道理。
三、結(jié)論
堅(jiān)持看完的小伙伴下次一定對(duì)裝飾器的原理了然于胸了。而且可能還有額外的收獲,比如類裝飾器的原理其實(shí)跟類實(shí)現(xiàn)了__call__()就可以像函數(shù)那樣調(diào)用的原理一樣,來看例子:
class CallTest: def __init__(self): pass def __call__(self, msg): print(msg) ct = CallTest() ct('I am called')
輸出:
I am called
這個(gè)例子可以明顯看出來,類在實(shí)例化出對(duì)象后,對(duì)象的參數(shù)其實(shí)就是傳給了__call__()了。一言以蔽之,Python的()運(yùn)算符其實(shí)就是__call__(),再來看上面類裝飾器裝飾的過程:
func = decorator(func) func(args)
第二步func(args)本質(zhì)上就是func.__call__(agrs),所以agrs只能傳給__call__()。
到此這篇關(guān)于Python中的各種裝飾器解析的文章就介紹到這了,更多相關(guān)Python裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
tensor和numpy的互相轉(zhuǎn)換的實(shí)現(xiàn)示例
這篇文章主要介紹了tensor和numpy的互相轉(zhuǎn)換的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08Python數(shù)據(jù)預(yù)處理時(shí)缺失值的不同處理方式總結(jié)
在使用python做數(shù)據(jù)分析的時(shí)候,經(jīng)常需要先對(duì)數(shù)據(jù)做統(tǒng)一化的處理,缺失值的處理是經(jīng)常會(huì)使用到的。今天介紹的是使用差補(bǔ)法/均值/固定值等不同的方式完成數(shù)據(jù)填充從而保證數(shù)據(jù)的完整性,感興趣的可以了解一下2022-12-12利用插件和python實(shí)現(xiàn)Excel轉(zhuǎn)json的兩種辦法
轉(zhuǎn)換Excel表格到JSON格式有很多方法,下面這篇文章主要給大家介紹了關(guān)于利用插件和python實(shí)現(xiàn)Excel轉(zhuǎn)json的兩種辦法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11Python實(shí)現(xiàn)PS圖像抽象畫風(fēng)效果的方法
這篇文章主要介紹了Python實(shí)現(xiàn)PS圖像抽象畫風(fēng)效果的方法,涉及Python基于skimage模塊進(jìn)行圖像處理的相關(guān)操作技巧,需要的朋友可以參考下2018-01-01如何使用python的subprocess執(zhí)行命令、交互、等待、是否結(jié)束及解析JSON結(jié)果
這篇文章主要給大家介紹了關(guān)于如何使用python的subprocess執(zhí)行命令、交互、等待、是否結(jié)束及解析JSON結(jié)果的相關(guān)資料,subprocess模塊提供了一種簡(jiǎn)單的方法來創(chuàng)建和管理子進(jìn)程,它可以讓我們?cè)赑ython程序中執(zhí)行外部命令,獲取命令的輸出和錯(cuò)誤信息,需要的朋友可以參考下2023-12-12django和vue實(shí)現(xiàn)數(shù)據(jù)交互的方法
今天小編就為大家分享一篇django和vue實(shí)現(xiàn)數(shù)據(jù)交互的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08python刪除指定目錄下的文件和文件夾的實(shí)現(xiàn)
在日常的辦公中,我們可以利用Python批量刪除文件和文件夾,本文就來介紹一下python刪除指定目錄下的文件和文件夾的實(shí)現(xiàn),感興趣的可以了解一下2024-01-01Python之tkinter面板PanedWindow的使用
這篇文章主要介紹了Python之tkinter面板PanedWindow的使用及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05