這篇文章主要介紹“Python中的裝飾器知識點有哪些”,在日常操作中,相信很多人在Python中的裝飾器知識點有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Python中的裝飾器知識點有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在勐海等地區(qū),都構建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務理念,為客戶提供成都網(wǎng)站建設、網(wǎng)站設計 網(wǎng)站設計制作定制開發(fā),公司網(wǎng)站建設,企業(yè)網(wǎng)站建設,品牌網(wǎng)站制作,全網(wǎng)營銷推廣,成都外貿(mào)網(wǎng)站建設,勐海網(wǎng)站建設費用合理。
要了解什么是裝飾器(decorator),我們首先需要知道閉包(closure)的概念。
閉包,又稱閉包函數(shù)或者閉合函數(shù),通俗一點來講,當某個函數(shù)被當成對象返回時還夾帶了外部變量,就形成了一個閉包。
以打印Hello World為例,我們先來看一下嵌套函數(shù)的結構應該是什么樣的:
def print_msg(msg): def printer(): print(msg) printer()print_msg('Hello World')# Hello World
執(zhí)行 print_msg('Hello World')
相當于執(zhí)行了 printer()
,也就是執(zhí)行 print(msg)
,所以將輸出 Hello World
。
我們再來看一下如果是閉包,該是什么樣的結構:
def print_msg(msg): def printer(): print(msg) return printer my_msg = print_msg('Hello World')my_msg()# Hello World
本例中的
printer
函數(shù)就是閉包。
執(zhí)行 print_msg('Hello World')
實際上是返回了如下這樣一個函數(shù),它夾帶了外部變量 'Hello World'
:
def printer(): print('Hello World')
于是調(diào)用 my_msg
就相當于執(zhí)行 printer()
。
那么如何判斷一個函數(shù)是否是閉包函數(shù)呢?閉包函數(shù)的 __closure__
屬性里面定義了一個元組用于存放所有的cell對象,每個cell對象保存了這個閉包中所有的外部變量。而普通函數(shù)的 __closure__
屬性為 None
。
def outer(content): def inner(): print(content) return innerprint(outer.__closure__) # Noneinner = outer('Hello World')print(inner.__closure__) # (,) |
由此可見 outer
函數(shù)不是閉包,而 inner
函數(shù)是閉包。
我們還可以查看閉包所攜帶的外部變量:
print(inner.__closure__[0].cell_contents)# Hello World
說了那么多,那么閉包究竟有什么用呢?閉包存在的意義就是它夾帶了外部變量(私貨),如果它不夾帶私貨,那么就和普通的函數(shù)沒有任何區(qū)別。
閉包的優(yōu)點如下:
局部變量無法共享和長久的保存,而全局變量可能造成變量污染,閉包既可以長久的保存變量又不會造成全局污染。
閉包使得函數(shù)內(nèi)局部變量的值始終保持在內(nèi)存中,不會在外部函數(shù)調(diào)用后被自動清除。
我們先考慮這樣一個場景,假設先前編寫的一個函數(shù)已經(jīng)實現(xiàn)了4個功能,為簡便起見,我們用 print
語句來代表每一個具體的功能:
def module(): print('功能1') print('功能2') print('功能3') print('功能4')
現(xiàn)在,由于某種原因,你需要為 module
這個函數(shù)新增一個 功能5
,你完全可以這樣修改:
def module(): print('功能1') print('功能2') print('功能3') print('功能4') print('功能5')
但在現(xiàn)實業(yè)務中,直接做出這樣的修改往往是比較危險的(會變得不易于維護)。那么如何在不修改原函數(shù)的基礎上去為它新添一個功能呢?
你可能已經(jīng)想到了使用之前的閉包知識:
def func_5(original_module): def wrapper(): original_module() print('功能5') return wrapper
func_5
代表該函數(shù)主要用于實現(xiàn) 功能5
,我們接下來將 module
傳入進去來觀察效果:
new_module = func_5(module)new_module()# 功能1# 功能2# 功能3# 功能4# 功能5
可以看出,我們的新模塊:new_module
已經(jīng)實現(xiàn)了 功能5
。
在上面的例子中,函數(shù)
func_5
就是一個裝飾器,它裝飾了原來的模塊(為它新添了一個功能)。
當然,Python有更簡潔的寫法(稱之為語法糖),我們可以將@符號與裝飾器函數(shù)的名稱一起使用,并將其放置在要裝飾的函數(shù)的定義上方:
def func_5(original_module): def wrapper(): original_module() print('功能5') return wrapper@func_5def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5
基于此,我們可以在不修改原函數(shù)的基礎上完成計時任務(計算原函數(shù)的運行時間),如下:
def timer(func): def wrapper(): import time tic = time.time() func() toc = time.time() print('程序用時: {}s'.format(toc - tic)) return wrapper@timerdef make_list(): return [i * i for i in range(10**7)]my_list = make_list()# 程序用時: 0.8369960784912109s
事實上,my_list
并不是列表,直接打印會顯示 None
,這是因為我們的 wrapper
函數(shù)沒有設置返回值。如果需要獲得 make_list
的返回值,可以這樣修改 wrapper
函數(shù):
def wrapper(): import time tic = time.time() a = func() toc = time.time() print('程序用時: {}s'.format(toc - tic)) return a
假如我們要為 module
新添 功能5
和 功能6
(按數(shù)字順序),那該如何做呢?
好在Python允許同時使用多個裝飾器:
def func_5(original_module): def wrapper(): original_module() print('功能5') return wrapperdef func_6(original_module): def wrapper(): original_module() print('功能6') return wrapper@func_6@func_5def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5# 功能6
上述過程實際上等價于:
def module(): print('功能1') print('功能2') print('功能3') print('功能4')new_module = func_6(func_5(module))new_module()
此外,需要注意的是,在使用多個裝飾器時,最靠近函數(shù)定義的裝飾器會最先裝飾該函數(shù),如果我們改變裝飾順序,則輸出結果也將改變:
@func_5@func_6def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能6# 功能5
如果被裝飾的函數(shù)帶有參數(shù),那該如何去構造裝飾器呢?
考慮這樣一個函數(shù):
def pide(a, b): return a / b
當b=0 時會出現(xiàn) ZeropisionError
。如何在避免修改該函數(shù)的基礎上給出一個更加人性化的提醒呢?
因為我們的 pide
函數(shù)接收兩個參數(shù),所以我們的 wrapper
函數(shù)也應當接收兩個參數(shù):
def smart_pide(func): def wrapper(a, b): if b == 0: return '被除數(shù)不能為0!' else: return func(a, b) return wrapper
使用該裝飾器進行裝飾:
@smart_pidedef pide(a, b): return a / bprint(pide(3, 0))# 被除數(shù)不能為0!print(pide(3, 1))# 3.0
如果不知道要被裝飾的函數(shù)有多少個參數(shù),我們可以使用下面更為通用的模板:
def decorator(func): def wrapper(*args, **kwargs): # ... res = func(*args, **kwargs) # ... return res # 也可以不return return wrapper
我們之前提到的裝飾器都沒有帶參數(shù),即語法糖 @decorator
中沒有參數(shù),那么該如何寫一個帶參數(shù)的裝飾器呢?
前面實現(xiàn)的裝飾器都是兩層嵌套函數(shù),而帶參數(shù)的裝飾器是一個三層嵌套函數(shù)。
考慮這樣一個場景。假如我們在為 module
添加新功能時,希望能夠加上實現(xiàn)該功能的開發(fā)人員的花名,則可以這樣構造裝飾器(以 功能5
為例):
def func_5_with_name(name=None): def func_5(original_module): def wrapper(): original_module() print('功能5由{}實現(xiàn)'.format(name)) return wrapper return func_5
效果如下:
@func_5_with_name(name='若水')def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水實現(xiàn)
對于這種三層嵌套函數(shù),我們可以這樣理解:當為 func_5_with_name
指定了參數(shù)后,func_5_with_name(name='若水')
實際上返回了一個 decorator
,于是 @func_5_with_name(name='若水')
就相當于 @decorator
。
將類作為裝飾器,我們需要實現(xiàn) __init__
方法和 __call__
方法。
以計時器為例,具體實現(xiàn)如下:
class Timer: def __init__(self, func): self.func = func def __call__(self): import time tic = time.time() self.func() toc = time.time() print('用時: {}s'.format(toc - tic))@Timerdef make_list(): return [i**2 for i in range(10**7)]make_list()# 用時: 2.928966999053955s
如果想要自定義生成列表的長度并獲得列表(即被裝飾的函數(shù)帶有參數(shù)情形),我們就需要在 __call__
方法中傳入相應的參數(shù),具體如下:
class Timer: def __init__(self, func): self.func = func def __call__(self, num): import time tic = time.time() res = self.func(num) toc = time.time() print('用時: {}s'.format(toc - tic)) return res@Timerdef make_list(num): return [i**2 for i in range(num)]my_list = make_list(10**7)# 用時: 2.8219943046569824sprint(len(my_list))# 10000000
如果要構建帶參數(shù)的類裝飾器,則不能把 func
傳入 __init__
中,而是傳入到 __call__
中,同時 __init__
用來初始化類裝飾器的參數(shù)。
接下來我們使用類裝飾器來復現(xiàn)第五章節(jié)中的效果:
class Func_5: def __init__(self, name=None): self.name = name def __call__(self, func): def wrapper(): func() print('功能5由{}實現(xiàn)'.format(self.name)) return wrapper@Func_5('若水')def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水實現(xiàn)
Python中有許多內(nèi)置裝飾器,這里僅介紹最常見的三種:@classmethod
、@staticmethod
和 @property
。
@classmethod
用于裝飾類中的函數(shù),使用它裝飾的函數(shù)不需要進行實例化也可調(diào)用。需要注意的是,被裝飾的函數(shù)不需要 self
參數(shù),但第一個參數(shù)需要是表示自身類的 cls
參數(shù),它可以來調(diào)用類的屬性,類的方法,實例化對象等。
cls
代表類本身,self
代表實例本身。
具體請看下例:
class A: num = 100 def func1(self): print('功能1') @classmethod def func2(cls): print('功能2') print(cls.num) cls().func1()A.func2()# 功能2# 100# 功能1
@staticmethod
同樣用來修飾類中的方法,使用它裝飾的函數(shù)的參數(shù)沒有任何限制(即無需傳入 self
參數(shù)),并且可以不用實例化調(diào)用該方法。當然,實例化后調(diào)用該方法也是允許的。
具體如下:
class A: @staticmethod def add(a, b): return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5
使用 @property
裝飾器,我們可以直接通過方法名來訪問類方法,不需要在方法名后添加一對 ()
小括號。
class A: @property def printer(self): print('Hello World')a = A()a.printer# Hello World
除此之外,@property
還可以用來防止類的屬性被修改??紤]如下場景
class A: def __init__(self): self.name = 'ABC'a = A()print(a.name)# ABCa.name = 1print(a.name)# 1
可以看出類中的屬性 name
可以被隨意修改。如果要防止修改,則可以這樣做
class A: def __init__(self): self.name_ = 'ABC' @property def name(self): return self.name_ a = A()print(a.name)# ABCa.name = 1print(a.name)# AttributeError: can't set attribute
到此,關于“Python中的裝飾器知識點有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
本文題目:Python中的裝飾器知識點有哪些
網(wǎng)站URL:http://weahome.cn/article/pjhdcd.html