Python中的各種裝飾器解析
前言
Python裝飾器可以在不改變函數(shù)原實現(xiàn)方式的前提下,為函數(shù)添加額外的功能。裝飾器的功能并不費解(func = decorator(func) ),但具體實現(xiàn)時有一些細(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
上面就是最簡單的裝飾器,值得注意的是:
- 不帶參的decorator裝飾函數(shù)時,勿帶括號@decorator_without_args
- 裝飾器外層函數(shù)的return不加括號,如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ù)個數(shù)必須保持一致,不一致就會報錯。這個特性可以理解為裝飾器的內(nèi)層函數(shù)(wrapper)有兩個使命:首先是添加額外的功能,其次是將被裝飾函數(shù)(func)的參數(shù)msg接收過來,以便最后rerun func(msg)。而第二點其實和裝飾器本身想實現(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ù)
實際項目中,除了func本身需要傳參外,有時候decorator也需要傳入?yún)?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ù) @decorator_with_args(True)
- 一共有三層,最外層用于接收裝飾器函數(shù)的參數(shù),內(nèi)層和不帶參的裝飾器一樣。因此可以得出結(jié)論:若裝飾器函數(shù)帶參,需要寫成三層,最外層用于接收裝飾器函數(shù)的參數(shù),中間一層接受被裝飾函數(shù)func,最里面一層用于接收被裝飾函數(shù)func的參數(shù)(*args, **kwargs)
二、類的裝飾器
表示在類上使用的裝飾器,類是被裝飾對象,請區(qū)分“類裝飾器”,舉個最經(jīng)典的例子—裝飾器實現(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ù)傳給裝飾器,最后裝飾器再返回一個實例對象。事實上,把Register視作一個函數(shù)就很好理解了,畢竟Register實例化時,也要執(zhí)行Register()。
三、類裝飾器
表示類作為裝飾器,此時裝飾器是用類實現(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
如果明白了裝飾器的原理,類裝飾器也很好理解。事實上,任何類型裝飾器,要起到作用,有兩個隱式前提:
- 裝飾器本身是一個可調(diào)用對象(callable),畢竟本質(zhì)上繞不開func = decorator(func)
- 必須向裝飾器傳入兩類參數(shù):一是func本身,而是func的參數(shù)(*args, **kwargs)
在Python中,對于類對象class,只要實現(xiàn)了__call__()方法,該類實例化后就可以像調(diào)用函數(shù)一樣使用這個實例。上面示例中,類Decorator實例化后可以像函數(shù)一樣被調(diào)用,這就滿足了裝飾器第一個前提。 對于第二個前提,眾所周知,類的初始化變量可以通過__init__()接收,因此,通過init將func傳入類中,而func中參數(shù)則被傳到了__call__()中。類的實例化過程想必大家也都知道,總是先執(zhí)行init(),因此I am initialing最先被打印出來。此處小伙伴可能有疑問了,可不可以將func和func中參數(shù)全都傳給__call__(),從而省去__init__()呢?或者說,可不可以將func和func中的參數(shù)全都傳給__init()__而省去__call__()呢?答案是不可以。事實上,裝飾的過程分為兩步:
func = decorator(func) func(args)
兩類參數(shù)(被裝飾函數(shù)本身,及其參數(shù))也是分兩次傳入裝飾器的。對于類裝飾器,第一步其實是實例化類裝飾器,所以func只能傳給__init__()。而func的args則必須傳給__call__(),至于為什么,請看下面結(jié)論。此處有沒有似曾相識的感覺?其實跟函數(shù)裝飾器func要傳給外層函數(shù),args傳給內(nèi)層函數(shù)一個道理。
三、結(jié)論
堅持看完的小伙伴下次一定對裝飾器的原理了然于胸了。而且可能還有額外的收獲,比如類裝飾器的原理其實跟類實現(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
這個例子可以明顯看出來,類在實例化出對象后,對象的參數(shù)其實就是傳給了__call__()了。一言以蔽之,Python的()運算符其實就是__call__(),再來看上面類裝飾器裝飾的過程:
func = decorator(func) func(args)
第二步func(args)本質(zhì)上就是func.__call__(agrs),所以agrs只能傳給__call__()。
到此這篇關(guān)于Python中的各種裝飾器解析的文章就介紹到這了,更多相關(guān)Python裝飾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在pycharm中debug 實時查看數(shù)據(jù)操作(交互式)
這篇文章主要介紹了在pycharm中debug 實時查看數(shù)據(jù)操作(交互式),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06
Kmeans均值聚類算法原理以及Python如何實現(xiàn)
這個算法中文名為k均值聚類算法,首先我們在二維的特殊條件下討論其實現(xiàn)的過程,方便大家理解。2020-09-09
python ceiling divide 除法向上取整(或小數(shù)向上取整)的實例
今天小編就為大家分享一篇python ceiling divide 除法向上取整 (或小數(shù)向上取整)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12
Python continue繼續(xù)循環(huán)用法總結(jié)
本篇文章給大家總結(jié)了關(guān)于Python continue繼續(xù)循環(huán)的相關(guān)知識點以及用法,有需要的朋友跟著學(xué)習(xí)下吧。2018-06-06
python 如何用map()函數(shù)創(chuàng)建多線程任務(wù)
這篇文章主要介紹了python 使用map()函數(shù)創(chuàng)建多線程任務(wù)的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
Python datetime 模塊概述及應(yīng)用場景
Python的datetime模塊是標(biāo)準(zhǔn)庫中用于處理日期和時間的核心模塊,本文給大家介紹Python datetime 模塊概述及應(yīng)用場景,感興趣的朋友一起看看吧2025-05-05

