日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

(一)Python装饰器的通俗理解

發(fā)布時間:2025/3/20 python 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (一)Python装饰器的通俗理解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在學(xué)習(xí)Python的過程中,我相信有很多人和我一樣,對Python的裝飾器一直覺得很困惑,我也是困惑了好久,并通過思考和查閱才能略有領(lǐng)悟,我希望以下的內(nèi)容會對你有幫助,我也努力通過通俗的方式使得對Python裝飾器的理解更加的透徹。在文中如有遺漏和不足,歡迎交流和指點。?
允許轉(zhuǎn)載并注明出處:http://blog.csdn.net/u013471155

很多人對裝飾器難以理解,原因是由于以下三點內(nèi)容沒有搞清楚:

  • 關(guān)于函數(shù)“變量”(或“變量”函數(shù))的理解
  • 關(guān)于高階函數(shù)的理解
  • 關(guān)于嵌套函數(shù)的理解
  • 那么如果能對以上的問題一一攻破,同時遵循裝飾器的基本原則,相信會對裝飾器有個很好的理解的。那么我們先來看以下裝飾器的目的及其原則。

    1、裝飾器

    裝飾器實際上就是為了給某程序增添功能,但該程序已經(jīng)上線或已經(jīng)被使用,那么就不能大批量的修改源代碼,這樣是不科學(xué)的也是不現(xiàn)實的,因為就產(chǎn)生了裝飾器,使得其滿足:

  • 不能修改被裝飾的函數(shù)的源代碼
  • 不能修改被裝飾的函數(shù)的調(diào)用方式
  • 滿足1、2的情況下給程序增添功能
  • 那么根據(jù)需求,同時滿足了這三點原則,這才是我們的目的。因為,下面我們從解決這三點原則入手來理解裝飾器。

    等等,我要在需求之前先說裝飾器的原則組成:

    < 函數(shù)+實參高階函數(shù)+返回值高階函數(shù)+嵌套函數(shù)+語法糖 = 裝飾器 >

    這個式子是貫穿裝飾器的靈魂所在!

    2、需求的實現(xiàn)

    假設(shè)有代碼:

    improt time def test():time.sleep(2)print("test is running!") test()

    ?

    很顯然,這段代碼運行的結(jié)果一定是:等待約2秒后,輸出

    test is running

    • 那么要求在滿足三原則的基礎(chǔ)上,給程序添加統(tǒng)計運行時間(2?second)功能

    在行動之前,我們先來看一下文章開頭提到的原因1(關(guān)于函數(shù)“變量”(或“變量”函數(shù))的理解)

    2.1、函數(shù)“變量”(或“變量”函數(shù))

    假設(shè)有代碼:

    x = 1 y = x def test1():print("Do something") test2 = lambda x:x*2

    ?

    那么在內(nèi)存中,應(yīng)該是這樣的:

    很顯然,函數(shù)和變量是一樣的,都是“一個名字對應(yīng)內(nèi)存地址中的一些內(nèi)容”?
    那么根據(jù)這樣的原則,我們就可以理解兩個事情:

  • test1表示的是函數(shù)的內(nèi)存地址
  • test1()就是調(diào)用對在test1這個地址的內(nèi)容,即函數(shù)
  • 如果這兩個問題可以理解,那么我們就可以進入到下一個原因(關(guān)于高階函數(shù)的理解)

    2.2高階函數(shù)

    那么對于高階函數(shù)的形式可以有兩種:

  • 把一個函數(shù)名當作實參傳給另外一個函數(shù)(“實參高階函數(shù)”)
  • 返回值中包含函數(shù)名(“返回值高階函數(shù)”)
  • 那么這里面所說的函數(shù)名,實際上就是函數(shù)的地址,也可以認為是函數(shù)的一個標簽而已,并不是調(diào)用,是個名詞。如果可以把函數(shù)名當做實參,那么也就是說可以把函數(shù)傳遞到另一個函數(shù),然后在另一個函數(shù)里面做一些操作,根據(jù)這些分析來看,這豈不是滿足了裝飾器三原則中的第一條,即不修改源代碼而增加功能。那我們看來一下具體的做法:

    還是針對上面那段代碼:

    improt timedef test():time.sleep(2)print("test is running!")def deco(func): start = time.time()func() #2stop = time.time()print(stop-start)deco(test) #1

    ?

    我們來看一下這段代碼,在#1處,我們把test當作實參傳遞給形參func,即func=test。注意,這里傳遞的是地址,也就是此時func也指向了之前test所定義的那個函數(shù)體,可以說在deco()內(nèi)部,func就是test。在#2處,把函數(shù)名后面加上括號,就是對函數(shù)的調(diào)用(執(zhí)行它)。因此,這段代碼運行結(jié)果是:

    test is running!?
    the run time is 3.0009405612945557

    我們看到似乎是達到了需求,即執(zhí)行了源程序,同時也附加了計時功能,但是這只滿足了原則1(不能修改被裝飾的函數(shù)的源代碼),但這修改了調(diào)用方式。假設(shè)不修改調(diào)用方式,那么在這樣的程序中,被裝飾函數(shù)就無法傳遞到另一個裝飾函數(shù)中去。

    那么再思考,如果不修改調(diào)用方式,就是一定要有test()這條語句,那么就用到了第二種高階函數(shù),即返回值中包含函數(shù)名

    如下代碼:improt timedef test():time.sleep(2)print("test is running!")def deco(func): print(func)return func t = deco(test) #3 #t()#4test()

      

    我們看這段代碼,在#3處,將test傳入deco(),在deco()里面操作之后,最后返回了func,并賦值給t。因此這里test => func => t,都是一樣的函數(shù)體。最后在#4處保留了原來的函數(shù)調(diào)用方式。?
    看到這里顯然會有些困惑,我們的需求不是要計算函數(shù)的運行時間么,怎么改成輸出函數(shù)地址了。是因為,單獨采用第二張高階函數(shù)(返回值中包含函數(shù)名)的方式,并且保留原函數(shù)調(diào)用方式,是無法計時的。如果在deco()里計時,顯然會執(zhí)行一次,而外面已經(jīng)調(diào)用了test(),會重復(fù)執(zhí)行。這里只是為了說明第二種高階函數(shù)的思想,下面才真的進入重頭戲。

    2.3 嵌套函數(shù)

    嵌套函數(shù)指的是在函數(shù)內(nèi)部定義一個函數(shù),而不是調(diào)用,如:

    def func1():def func2():pass 而不是 def func1():func2()

    ?

    另外還有一個題外話,函數(shù)只能調(diào)用和它同級別以及上級的變量或函數(shù)。也就是說:里面的能調(diào)用和它縮進一樣的和他外部的,而內(nèi)部的是無法調(diào)用的。

    那么我們再回到我們之前的那個需求,想要統(tǒng)計程序運行時間,并且滿足三原則。

    代碼:

    improt timedef timer(func) #5def deco(): start = time.time()func()stop = time.time()print(stop-start)return decotest = timer(test) #6def test():time.sleep(2)print("test is running!") test() #7

    ?

    這段代碼可能會有些困惑,怎么忽然多了這么多,暫且先接受它,分析一下再來說為什么是這樣。

    首先,在#6處,把test作為參數(shù)傳遞給了timer(),此時,在timer()內(nèi)部,func = test,接下來,定義了一個deco()函數(shù),當并未調(diào)用,只是在內(nèi)存中保存了,并且標簽為deco。在timer()函數(shù)的最后返回deco()的地址deco。

    然后再把deco賦值給了test,那么此時test已經(jīng)不是原來的test了,也就是test原來的那些函數(shù)體的標簽換掉了,換成了deco。那么在#7處調(diào)用的實際上是deco()。

    那么這段代碼在本質(zhì)上是修改了調(diào)用函數(shù),但在表面上并未修改調(diào)用方式,而且實現(xiàn)了附加功能。

    那么通俗一點的理解就是:

    把函數(shù)看成是盒子,test是小盒子,deco是中盒子,timer是大盒子。程序中,把小盒子test傳遞到大盒子temer中的中盒子deco,然后再把中盒子deco拿出來,打開看看(調(diào)用)

    這樣做的原因是:

    我們要保留test(),還要統(tǒng)計時間,而test()只能調(diào)用一次(調(diào)用兩次運行結(jié)果會改變,不滿足),再根據(jù)函數(shù)即“變量”,那么就可以通過函數(shù)的方式來回閉包。于是乎,就想到了,把test傳遞到某個函數(shù),而這個函數(shù)內(nèi)恰巧內(nèi)嵌了一個內(nèi)函數(shù),再根據(jù)內(nèi)嵌函數(shù)的作用域(可以訪問同級及以上,內(nèi)嵌函數(shù)可以訪問外部參數(shù)),把test包在這個內(nèi)函數(shù)當中,一起返回,最后調(diào)用這個返回的函數(shù)。而test傳遞進入之后,再被包裹出來,顯然test函數(shù)沒有弄丟(在包裹里),那么外面剩下的這個test標簽正好可以替代這個包裹(內(nèi)含test())。

    至此,一切皆合,大功告成,單只差一步。

    3、 真正的裝飾器

    根據(jù)以上分析,裝飾器在裝飾時,需要在每個函數(shù)前面加上:

    test = timer(test)

    顯然有些麻煩,Python提供了一種語法糖,即:

    @timer

    這兩句是等價的,只要在函數(shù)前加上這句,就可以實現(xiàn)裝飾作用。

    以上為無參形式

    4、裝飾有參函數(shù)

    improt timedef timer(func)def deco(): start = time.time()func()stop = time.time()print(stop-start)return deco@timer def test(parameter): #8time.sleep(2)print("test is running!") test()

    ?

    對于一個實際問題,往往是有參數(shù)的,如果要在#8處,給被修飾函數(shù)加上參數(shù),顯然這段程序會報錯的。錯誤原因是test()在調(diào)用的時候缺少了一個位置參數(shù)的。而我們知道test = func = deco,因此test()=func()=deco()?
    ,那么當test(parameter)有參數(shù)時,就必須給func()和deco()也加上參數(shù),為了使程序更加有擴展性,因此在裝飾器中的deco()和func(),加如了可變參數(shù)*agrs和 **kwargs。

    完整代碼如下:

    improt timedef timer(func)def deco(*args, **kwargs): start = time.time()func(*args, **kwargs)stop = time.time()print(stop-start)return deco@timer def test(parameter): #8time.sleep(2)print("test is running!") test()

    ?

    那么我們再考慮個問題,如果原函數(shù)test()的結(jié)果有返回值呢?比如:

    def test(parameter): time.sleep(2)print("test is running!") return "Returned value"

    ?

    那么面對這樣的函數(shù),如果用上面的代碼來裝飾,最后一行的test()實際上調(diào)用的是deco()。有人可能會問,func()不就是test()么,怎么沒返回值呢?

    其實是有返回值的,但是返回值返回到deco()的內(nèi)部,而不是test()即deco()的返回值,那么就需要再返回func()的值,因此就是:

    def timer(func)def deco(*args, **kwargs): start = time.time()res = func(*args, **kwargs)#9stop = time.time()print(stop-start)return res#10return deco

    ?

    其中,#9的值在#10處返回。

    完整程序為:

    improt timedef timer(func)def deco(*args, **kwargs): start = time.time()res = func(*args, **kwargs)stop = time.time()print(stop-start)return res return deco@timer def test(parameter): #8time.sleep(2)print("test is running!") return "Returned value" test()

    ?

    5、帶參數(shù)的裝飾器

    又增加了一個需求,一個裝飾器,對不同的函數(shù)有不同的裝飾。那么就需要知道對哪個函數(shù)采取哪種裝飾。因此,就需要裝飾器帶一個參數(shù)來標記一下。例如:

    @decorator(parameter = value)

    比如有兩個函數(shù):

    def task1():time.sleep(2)print("in the task1")def task2():time.sleep(2)print("in the task2")task1() task2()

    ?

    要對這兩個函數(shù)分別統(tǒng)計運行時間,但是要求統(tǒng)計之后輸出:

    the task1/task2 run time is : 2.00……

    于是就要構(gòu)造一個裝飾器timer,并且需要告訴裝飾器哪個是task1,哪個是task2,也就是要這樣:

    @timer(parameter='task1') # def task1():time.sleep(2)print("in the task1")@timer(parameter='task2') # def task2():time.sleep(2)print("in the task2")task1() task2()

    ?

    那么方法有了,但是我們需要考慮如何把這個parameter參數(shù)傳遞到裝飾器中,我們以往的裝飾器,都是傳遞函數(shù)名字進去,而這次,多了一個參數(shù),要怎么做呢??
    于是,就想到再加一層函數(shù)來接受參數(shù),根據(jù)嵌套函數(shù)的概念,要想執(zhí)行內(nèi)函數(shù),就要先執(zhí)行外函數(shù),才能調(diào)用到內(nèi)函數(shù),那么就有:

    def timer(parameter): # print("in the auth :", parameter)def outer_deco(func): # print("in the outer_wrapper:", parameter)def deco(*args, **kwargs):return decoreturn outer_deco

    ?

    首先timer(parameter),接收參數(shù)parameter=’task1/2’,而@timer(parameter)也恰巧帶了括號,那么就會執(zhí)行這個函數(shù), 那么就是相當于:

    timer = timer(parameter) task1 = timer(task1)

    ?

    后面的運行就和一般的裝飾器一樣了:

    import timedef timer(parameter):def outer_wrapper(func):def wrapper(*args, **kwargs):if parameter == 'task1':start = time.time()func(*args, **kwargs)stop = time.time()print("the task1 run time is :", stop - start)elif parameter == 'task2':start = time.time()func(*args, **kwargs)stop = time.time()print("the task2 run time is :", stop - start)return wrapperreturn outer_wrapper@timer(parameter='task1') def task1():time.sleep(2)print("in the task1")@timer(parameter='task2') def task2():time.sleep(2)print("in the task2")task1() task2()

    ?

    至此,裝飾器的全部內(nèi)容結(jié)束。

    轉(zhuǎn)載于:https://www.cnblogs.com/insane-Mr-Li/p/9062059.html

    總結(jié)

    以上是生活随笔為你收集整理的(一)Python装饰器的通俗理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。