欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Python裝飾器由淺入深

 更新時間:2016年12月09日 11:36:30   作者:銀河系1234  
裝飾器的功能在很多語言中都有,名字也不盡相同,其實它體現(xiàn)的是一種設(shè)計模式,強調(diào)的是開放封閉原則,更多的用于后期功能升級而不是編寫新的代碼。本文盡量描述得淺顯易懂,從最基礎(chǔ)的內(nèi)容講起。

裝飾器的功能在很多語言中都有,名字也不盡相同,其實它體現(xiàn)的是一種設(shè)計模式,強調(diào)的是開放封閉原則,更多的用于后期功能升級而不是編寫新的代碼。裝飾器不光能裝飾函數(shù),也能裝飾其他的對象,比如類,但通常,我們以裝飾函數(shù)為例子介紹其用法。要理解在Python中裝飾器的原理,需要一步一步來。本文盡量描述得淺顯易懂,從最基礎(chǔ)的內(nèi)容講起。

(注:以下使用Python3.5.1環(huán)境)

一、Python的函數(shù)相關(guān)基礎(chǔ)

第一,必須強調(diào)的是python是從上往下順序執(zhí)行的,而且碰到函數(shù)的定義代碼塊是不會立即執(zhí)行它的,只有等到該函數(shù)被調(diào)用時,才會執(zhí)行其內(nèi)部的代碼塊。

def foo():
print("foo函數(shù)被運行了!") 
如果就這么樣,foo里的語句是不會被執(zhí)行的。
程序只是簡單的將定義代碼塊讀入內(nèi)存中。

再看看,順序執(zhí)行的例子:

def foo():
 print("我是上面的函數(shù)定義!")
def foo():
 print("我是下面的函數(shù)定義!")
foo()
運行結(jié)果:
我是下面的函數(shù)定義

可見,因為順序執(zhí)行的原因,下面的foo將上面的foo覆蓋了。因此,在Python中代碼的放置位置是有要求的,不能隨意擺放,函數(shù)體要放在被調(diào)用的語句之前。

 其次,我們還要先搞清楚幾樣?xùn)|西:函數(shù)名、函數(shù)體、返回值,函數(shù)的內(nèi)存地址、函數(shù)名加括號、函數(shù)名被當(dāng)作參數(shù)、函數(shù)名加括號被當(dāng)作參數(shù)、返回函數(shù)名、返回函數(shù)名加括號。對于如下的函數(shù):

 def foo():
 print("讓我們干點啥!")
 return "ok"
 foo()  

     函數(shù)名:        foo

  函數(shù)體:        第1-3行

  返回值:        字符串“ok”    如果不顯式給出return的對象,那么默認返回None

  函數(shù)的內(nèi)存地址:    當(dāng)函數(shù)體被讀進內(nèi)存后的保存位置,它由標(biāo)識符即函數(shù)名foo引用,
                                                   也就是說foo指向的是函數(shù)體在內(nèi)存內(nèi)的保存位置。

  函數(shù)名加括號:       例如foo(),函數(shù)的調(diào)用方法,只有見到這個括號,程序會根據(jù)
                                                   函數(shù)名從內(nèi)存中找到函數(shù)體,然后執(zhí)行它

再看下面這個例子:

def outer(func):
 def inner():
 print("我是內(nèi)層函數(shù)!")
 return inner
def foo():
 print("我是原始函數(shù)!") 
outer(foo)
outer(foo())

在python中,一切都是對象,函數(shù)也不例外。因此可以將函數(shù)名,甚至函數(shù)名加括號進行調(diào)用的方式作為另一個函數(shù)的返回值。上面代碼中,outer和foo是兩個函數(shù),outer(foo)表示將foo函數(shù)的函數(shù)名當(dāng)做參數(shù)傳遞給outer函數(shù)并執(zhí)行outer函數(shù);outer(foo())表示將foo函數(shù)執(zhí)行后的返回值當(dāng)做參數(shù)傳遞給outer函數(shù)并執(zhí)行outer函數(shù),由于foo函數(shù)沒有指定返回值,實際上它傳遞給了outer函數(shù)一個None。注意其中的差別,有沒有括號是關(guān)鍵!

 同樣,在outer函數(shù)內(nèi)部,返回了一個inner,它是在outer函數(shù)內(nèi)部定義的一個函數(shù),注意,由于inner后面沒有加括號,所以返回的是inner的函數(shù)體,實際上也就是inner這個名字,一個簡單的引用而已。那么,如果outer函數(shù)返回的是inner()呢?現(xiàn)在你應(yīng)該已經(jīng)很清楚了,它會先執(zhí)行inner函數(shù)的內(nèi)容,然后返回個None給outer,outer再把這個None返回給調(diào)用它的對象。

 請記住,函數(shù)名、函數(shù)加括號可以被當(dāng)做參數(shù)傳遞,也可以被當(dāng)做返回值return,有沒有括號是兩個截然不同的意思!

二、裝飾器的使用場景

    裝飾器通常用于在不改變原有函數(shù)代碼和功能的情況下,為其添加額外的功能。比如在原函數(shù)執(zhí)行前先執(zhí)行點什么,在執(zhí)行后執(zhí)行點什么。

 讓我們通過一個例子來看看,裝飾器的使用場景和體現(xiàn)的設(shè)計模式。(抱歉的是我設(shè)計不出更好的場景,只能引用武大神的案例加以演繹)

 有一個大公司,下屬的基礎(chǔ)平臺部負責(zé)內(nèi)部應(yīng)用程序及API的開發(fā),有上百個業(yè)務(wù)部門負責(zé)不同的業(yè)務(wù),他們各自調(diào)用基礎(chǔ)平臺部提供的不同函數(shù)處理自己的業(yè)務(wù),情況如下: 

# 基礎(chǔ)平臺部門開發(fā)了上百個函數(shù)
def f1():
 print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
def f2():
 print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
def f3():
 print("業(yè)務(wù)部門3數(shù)據(jù)接口......")
def f100():
 print("業(yè)務(wù)部門100數(shù)據(jù)接口......") 
#各部門分別調(diào)用
f1()
f2()
f3()
f100()

     由于公司在創(chuàng)業(yè)初期,基礎(chǔ)平臺部開發(fā)這些函數(shù)時,由于各種原因,比如時間,比如考慮不周等等,沒有為函數(shù)調(diào)用進行安全認證?,F(xiàn)在,平臺部主管決定彌補這個缺陷,于是:

  第一回,主管叫來了一個運維工程師,工程師跑上跑下逐個部門進行通知,讓他們在代碼里加上認證功能,然而,當(dāng)天他被開除了。

 第二回:主管又叫來了一個運維工程師,工程師用shell寫了個復(fù)雜的腳本,勉強實現(xiàn)了功能。但他很快就回去接著做運維了,不會開發(fā)的運維不是好運維....

 第三回:主管叫來了一個python自動化開發(fā)工程師,哥們是這么干的:只對基礎(chǔ)平臺的代碼進行重構(gòu),讓N個業(yè)務(wù)部門無需做任何修改。這哥們很快也被開了,連運維也沒得做?! ?/p>

def f1():
 #加入認證程序代碼
 print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
def f2():
 # 加入認證程序代碼
 print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
def f3():
 # 加入認證程序代碼
 print("業(yè)務(wù)部門3數(shù)據(jù)接口......")
def f100():
 #加入認證程序代碼
 print("業(yè)務(wù)部門100數(shù)據(jù)接口......")
#各部門分別調(diào)用
f1()
f2()
f3()
f100()

 第四回:主管又換了個 工程師,他是這么干的:定義個認證函數(shù),原來其他的函數(shù)調(diào)用它,代碼如下框。但是,主管依然不滿意,不過這一次他解釋了為什么。主管說:寫代碼要遵循開放封閉原則,雖然在這個原則主要是針對面向?qū)ο箝_發(fā),但是也適用于函數(shù)式編程,簡單來說,它規(guī)定已經(jīng)實現(xiàn)的功能代碼內(nèi)部不允許被修改,但外部可以被擴展,即:封閉:已實現(xiàn)的功能代碼塊;開放:對擴展開放。如果將開放封閉原則應(yīng)用在上述需求中,那么就不允許在函數(shù) f1 、f2、f3......f100的內(nèi)部進行代碼修改。遺憾的是,工程師沒有漂亮的女朋友,所以很快也被開除了。

def login():
 print("認證成功!")
def f1():
 login()
 print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
def f2():
 login()
 print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
def f3():
 login()
 print("業(yè)務(wù)部門3數(shù)據(jù)接口......")
def f100():
 login()
 print("業(yè)務(wù)部門100數(shù)據(jù)接口......")
#各部門分別調(diào)用
f1()
f2()
f3()
f100()

    第五回:已經(jīng)沒有時間讓主管找別人來干這活了,他決定親自上陣,并且打算在函數(shù)執(zhí)行后再增加個日志功能。主管是這么想的:不會裝飾器的主管不是好碼農(nóng)!要不為啥我能當(dāng)主管,你只能被管呢?嘿嘿。他的代碼如下:

#/usr/bin/env python
#coding:utf-8
def outer(func):
 def inner():
 print("認證成功!")
 result = func()
 print("日志添加成功")
 return result
 return inner
@outer
def f1():
 print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
@outer
def f2():
 print("業(yè)務(wù)部門2數(shù)據(jù)接口......")
@outer
def f3():
 print("業(yè)務(wù)部門3數(shù)據(jù)接口......")
@outer
def f100():
 print("業(yè)務(wù)部門100數(shù)據(jù)接口......")
#各部門分別調(diào)用
f1()
f2()
f3()
f100()

對于上述代碼,也是僅需對基礎(chǔ)平臺的代碼進行拓展,就可以實現(xiàn)在其他部門調(diào)用函數(shù) f1 f2 f3 f100 之前都進行認證操作,在操作結(jié)束后保存日志,并且其他業(yè)務(wù)部門無需他們自己的代碼做任何修改,調(diào)用方式也不用變?!爸鞴堋睂懲甏a后,覺得獨樂了不如眾樂樂,打算顯擺一下,于是寫了篇博客將過程進行了詳細的說明。

 三、裝飾器的內(nèi)部原理、

 下面我們以f1函數(shù)為例進行說明:

 def outer(func):
 def inner():
 print("認證成功!")
 result = func()
 print("日志添加成功")
 return result
 return inner
@outer
def f1():
 print("業(yè)務(wù)部門1數(shù)據(jù)接口......")

 運用我們在第一部分介紹的知識來分析一下上面這段代碼:

  • 程序開始運行,從上往下編譯,讀到def outer(func):的時候,發(fā)現(xiàn)這是個“一等公民”->函數(shù),于是把函數(shù)體加載到內(nèi)存里,然后過。
  • 讀到@outer的時候,程序被@這個語法糖吸引住了,知道這是個裝飾器,按規(guī)矩要立即執(zhí)行的,于是程序開始運行@后面那個名字outer所定義的函數(shù)。(相信沒有人會愚蠢的將@outer寫到別的位置,它只能放在被裝飾的函數(shù)的上方最近處,不要空行。)
  • 程序返回到outer函數(shù),開始執(zhí)行裝飾器的語法規(guī)則,這部分規(guī)則是定死的,是python的“法律”,不要問為什么。規(guī)則是:被裝飾的函數(shù)的名字會被當(dāng)作參數(shù)傳遞給裝飾函數(shù)。裝飾函數(shù)執(zhí)行它自己內(nèi)部的代碼后,會將它的返回值賦值給被裝飾的函數(shù)。 

如下圖所示:

這里面需要注意的是:

  • @outer和@outer()有區(qū)別,沒有括號時,outer函數(shù)依然會被執(zhí)行,這和傳統(tǒng)的用括號才能調(diào)用函數(shù)不同,需要特別注意!那么有括號呢?那是裝飾器的高級用法了,以后會介紹。
  • 是f1這個函數(shù)名(而不是f1()這樣被調(diào)用后)當(dāng)做參數(shù)傳遞給裝飾函數(shù)outer,也就是:func = f1,@outer等于outer(f1),實際上傳遞了f1的函數(shù)體,而不是執(zhí)行f1后的返回值。
  • outer函數(shù)return的是inner這個函數(shù)名,而不是inner()這樣被調(diào)用后的返回值。

如果你對第一部分函數(shù)的基礎(chǔ)知識有清晰的了解,那么上面的內(nèi)容你應(yīng)該很容易理解。

 4. 程序開始執(zhí)行outer函數(shù)內(nèi)部的內(nèi)容,一開始它又碰到了一個函數(shù),很繞是吧?當(dāng)然,你可以在 inner函數(shù)前后安排點別的代碼,但它們不是重點,而且有點小麻煩,下面會解釋。inner函數(shù)定義塊被程序觀察到后不會立刻執(zhí)行,而是讀入內(nèi)存中(這是潛規(guī)則)。

 5. 再往下,碰到return inner,返回值是個函數(shù)名,并且這個函數(shù)名會被賦值給f1這個被裝飾的函數(shù),也就是f1 = inner。根據(jù)前面的知識,我們知道,此時f1函數(shù)被新的函數(shù)inner覆蓋了(實際上是f1這個函數(shù)名更改成指向inner這個函數(shù)名指向的函數(shù)體內(nèi)存地址,f1不再指向它原來的函數(shù)體的內(nèi)存地址),再往后調(diào)用f1的時候?qū)?zhí)行inner函數(shù)內(nèi)的代碼,而不是先前的函數(shù)體。那么先前的函數(shù)體去哪了?還記得我們將f1當(dāng)做參數(shù)傳遞給func這個形參么?func這個變量保存了老的函數(shù)在內(nèi)存中的地址,通過它就可以執(zhí)行 老的函數(shù)體,你能在inner函數(shù)里看到result = func()這句代碼,它就是這么干的!

 6.接下來,還沒有結(jié)束。當(dāng)業(yè)務(wù)部門,依然通過f1()的方式調(diào)用f1函數(shù)時,執(zhí)行的就不再是老的f1函數(shù)的代碼,而是inner函數(shù)的代碼。在本例中,它首先會打印個“認證成功”的提示,很顯然你可以換成任意的代碼,這只是個示例;然后,它會執(zhí)行func函數(shù)并將返回值賦值個變量result,這個func函數(shù)就是老的f1函數(shù);接著,它又打印了“日志保存”的提示,這也只是個示例,可以換成任何你想要的;最后返回result這個變量。我們在業(yè)務(wù)部門的代碼上可以用 r = f1()的方式接受result的值。

 7.以上流程走完后,你應(yīng)該看出來了,在沒有對業(yè)務(wù)部門的代碼和接口調(diào)用方式做任何修改的同時,也沒有對基礎(chǔ)平臺部原有的代碼做內(nèi)部修改,僅僅是添加了一個裝飾函數(shù),就實現(xiàn)了我們的需求,在函數(shù)調(diào)用前先認證,調(diào)用后寫入日志。這就是裝飾器的最大作用。

 問題:那么為什么我們要搞一個outer函數(shù)一個inner函數(shù)這么復(fù)雜呢?一層函數(shù)不行嗎?

 答:請注意,@outer這句代碼在程序執(zhí)行到這里的時候就會自動執(zhí)行outer函數(shù)內(nèi)部的代碼,如果不封裝一下,在業(yè)務(wù)部門還未進行調(diào)用的時候,就執(zhí)行了些什么,這和初衷有點不符。當(dāng)然,如果你對這個有需求也不是不行。請看下面的例子,它只有一層函數(shù)。

def outer(func):
 print("認證成功!")
 result = func()
 print("日志添加成功")
 return result
@outer
def f1():
 print("業(yè)務(wù)部門1數(shù)據(jù)接口......")
# 業(yè)務(wù)部門并沒有開始執(zhí)行f1函數(shù)
執(zhí)行結(jié)果:
認證成功!
業(yè)務(wù)部門1數(shù)據(jù)接口......
日志添加成功

看到?jīng)]?我只是定義好了函數(shù),業(yè)務(wù)部門還沒有調(diào)用f1函數(shù)呢,程序就把工作全做了。這就是封裝一層函數(shù)的原因。

四、裝飾器的參數(shù)傳遞

 細心的朋友可能已經(jīng)發(fā)現(xiàn)了,上面的例子中,f1函數(shù)沒有參數(shù),在實際情況中肯定會需要參數(shù)的,那參數(shù)怎么傳遞的呢?

 一個參數(shù)的情況:

def outer(func):
 def inner(username):
 print("認證成功!")
 result = func(username)
 print("日志添加成功")
 return result
 return inner
@outer
def f1(name):
print("%s 正在連接業(yè)務(wù)部門1數(shù)據(jù)接口......"%name)
# 調(diào)用方法
f1("jack")

在inner函數(shù)的定義部分也加上一個參數(shù),調(diào)用func函數(shù)的時候傳遞這個參數(shù),很好理解吧?可問題又來了,那么另外一個部門調(diào)用的f2有2個參數(shù)呢?f3有3個參數(shù)呢?你怎么傳遞?

很簡單,我們有*args和**kwargs嘛!號稱“萬能參數(shù)”!簡單修改一下上面的代碼:

def outer(func):
 def inner(*args,**kwargs):
 print("認證成功!")
 result = func(*args,**kwargs)
 print("日志添加成功")
 return result
 return inner
@outer
def f1(name,age):
 print("%s 正在連接業(yè)務(wù)部門1數(shù)據(jù)接口......"%name)
# 調(diào)用方法
f1("jack",18)

五、更進一步的思考

 一個函數(shù)可以被多個函數(shù)裝飾嗎?可以的!看下面的例子!  

def outer1(func):
 def inner(*args,**kwargs):
 print("認證成功!")
 result = func(*args,**kwargs)
 print("日志添加成功")
 return result
 return inner
def outer2(func):
 def inner(*args,**kwargs):
 print("一條歡迎信息。。。")
 result = func(*args,**kwargs)
 print("一條歡送信息。。。")
 return result
 return inner
 @outer1
@outer2
def f1(name,age):
 print("%s 正在連接業(yè)務(wù)部門1數(shù)據(jù)接口......"%name)
# 調(diào)用方法
f1("jack",18) 
執(zhí)行結(jié)果:
認證成功!
一條歡迎信息。。。
jack 正在連接業(yè)務(wù)部門1數(shù)據(jù)接口......
一條歡送信息。。。
日志添加成功

更進一步的,裝飾器自己可以有參數(shù)嗎?可以的!看下面的例子:

# 認證函數(shù)
def auth(request,kargs):
 print("認證成功!")
# 日志函數(shù)
def log(request,kargs):
 print("日志添加成功")
# 裝飾器函數(shù)。接收兩個參數(shù),這兩個參數(shù)應(yīng)該是某個函數(shù)的名字。
def Filter(auth_func,log_func):
 # 第一層封裝,f1函數(shù)實際上被傳遞給了main_fuc這個參數(shù)
 def outer(main_func):
 # 第二層封裝,auth和log函數(shù)的參數(shù)值被傳遞到了這里
 def wrapper(request,kargs):
 # 下面代碼的判斷邏輯不重要,重要的是參數(shù)的引用和返回值
 before_result = auth(request,kargs)
 if(before_result != None):
 return before_result;
 main_result = main_func(request,kargs)
 if(main_result != None):
 return main_result;
 after_result = log(request,kargs)
 if(after_result != None):
 return after_result;
 return wrapper
 return outer
# 注意了,這里的裝飾器函數(shù)有參數(shù)哦,它的意思是先執(zhí)行filter函數(shù)
# 然后將filter函數(shù)的返回值作為裝飾器函數(shù)的名字返回到這里,所以,
# 其實這里,F(xiàn)ilter(auth,log) = outer , @Filter(auth,log) = @outer
@Filter(auth,log)
def f1(name,age):
 print("%s 正在連接業(yè)務(wù)部門1數(shù)據(jù)接口......"%name)
# 調(diào)用方法
f1("jack",18)
運行結(jié)果:
認證成功!
jack 正在連接業(yè)務(wù)部門1數(shù)據(jù)接口......
日志添加成功

又繞暈了?其實你可以這么理解,先執(zhí)行Filter函數(shù),獲得它的返回值outer,再執(zhí)行@outer裝飾器語法。

以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!

相關(guān)文章

  • Python用來做Web開發(fā)的優(yōu)勢有哪些

    Python用來做Web開發(fā)的優(yōu)勢有哪些

    這篇文章主要介紹了Python用來做Web開發(fā)的優(yōu)勢有哪些,文中講解非常細致,幫助大家更好的理解和學(xué)習(xí)Python,感興趣的朋友可以了解下
    2020-08-08
  • Python的randrange()方法使用教程

    Python的randrange()方法使用教程

    這篇文章主要介紹了Python的randrange()方法使用教程,是Python學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-05-05
  • PyQt5 QThread倒計時功能的實現(xiàn)代碼

    PyQt5 QThread倒計時功能的實現(xiàn)代碼

    這篇文章主要介紹了PyQt5 QThread倒計時功能的實現(xiàn)代碼,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • python數(shù)組如何添加整行或整列

    python數(shù)組如何添加整行或整列

    這篇文章主要介紹了python數(shù)組如何添加整行或整列問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 一文輕松掌握python語言命名規(guī)范規(guī)則

    一文輕松掌握python語言命名規(guī)范規(guī)則

    這篇文章主要介紹了一文輕松掌握python語言命名規(guī)范規(guī)則,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Python實現(xiàn)制度轉(zhuǎn)換(貨幣,溫度,長度)

    Python實現(xiàn)制度轉(zhuǎn)換(貨幣,溫度,長度)

    這篇文章主要介紹了Python實現(xiàn)制度轉(zhuǎn)換(貨幣,溫度,長度),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • Python獲取當(dāng)前函數(shù)名稱方法實例分享

    Python獲取當(dāng)前函數(shù)名稱方法實例分享

    這篇文章主要介紹了Python獲取當(dāng)前函數(shù)名稱方法實例分享,具有一定借鑒價值
    2018-01-01
  • Python設(shè)計模式之橋接模式原理與用法實例分析

    Python設(shè)計模式之橋接模式原理與用法實例分析

    這篇文章主要介紹了Python設(shè)計模式之橋接模式原理與用法,結(jié)合具體實例形式分析了Python橋接模式的相關(guān)概念、原理、定義及使用方法,需要的朋友可以參考下
    2019-01-01
  • Python字典操作簡明總結(jié)

    Python字典操作簡明總結(jié)

    這篇文章主要介紹了Python字典操作簡明總結(jié),本文總結(jié)了創(chuàng)建字典 、創(chuàng)建一個"默認"字典、遍歷字典、獲得value值、成員操作符:in或not in 、更新字典、刪除字典等常用操作,需要的朋友可以參考下
    2015-04-04
  • Pytho的HTTP交互httpx包模塊使用詳解

    Pytho的HTTP交互httpx包模塊使用詳解

    Python 的 httpx 包是一個用于 HTTP 交互的一個優(yōu)秀且靈活的模塊。本文進行詳細的講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03

最新評論