<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          5分鐘掌握 Python 中的裝飾器

          共 5613字,需瀏覽 12分鐘

           ·

          2021-02-11 15:25

          ↑↑↑關(guān)注后"星標(biāo)"簡說Python
          人人都可以簡單入門Python、爬蟲、數(shù)據(jù)分析
          ?簡說Python推薦?
          來源:Python中文社區(qū)
          作者:鞏慶奎


          python中的裝飾器用于修飾函數(shù),以增強函數(shù)的行為:記錄函數(shù)執(zhí)行時間,建立和撤銷環(huán)境,記錄日志等。裝飾器可以在不修改函數(shù)內(nèi)部代碼的前提下實現(xiàn)以上增強行為。如下代碼建立一個計時裝飾器,隨后描述其工作原理。
          import?time?


          def?timethis(func):
          ????def?inner(*args,**kwargs):
          ????????print('start?timer:')
          ????????start?=?time.time()
          ????????result?=?func(*args,**kwargs)?
          ????????end?=?time.time()
          ????????print('end?timer:%fs.'%(end?-?start))
          ????????return?result?
          ????return?inner

          @timethis
          def?sleeps(seconds):
          ????print('??sleeps?begin:')
          ????time.sleep(seconds)
          ????print('????sleep?%d?seconds.\n??sleeps?over.'%seconds)
          ????return?seconds

          print(sleeps(3))
          執(zhí)行以上代碼,輸出:
          start?timer:
          ??sleeps?begin:
          ????sleep?3?seconds.
          ??sleeps?over.
          end?timer:3.002512s.
          3
          可見,timethis裝飾器實現(xiàn)了為sleeps函數(shù)計時的功能。其關(guān)鍵在于@標(biāo)識符的使用。

          一、理解@標(biāo)識符

          @標(biāo)識符是Pyton的語法糖,定義被裝飾函數(shù)時使用@timethis修飾和用語句sleeps = timethis(sleeps)是等價的。
          @timethis
          def?sleeps(seconds):
          ????print('??sleeps?begin:')
          ????time.sleep(seconds)
          ????print('????sleep?%d?seconds.\n??sleeps?over.'%seconds)
          ????return?seconds

          相當(dāng)于
          def?sleeps(seconds):
          ????print('??sleeps?begin:')
          ????time.sleep(seconds)
          ????print('????sleep?%d?seconds.\n??sleeps?over.'%seconds)
          ????return?seconds
          ????
          sleeps?=?timethis(sleeps)
          @語法只是裝飾器調(diào)用的便捷方式:將被裝飾函數(shù)sleeps作為參數(shù)傳給裝飾器函數(shù),再將裝飾器返回值重新綁定到原sleeps變量上。理解了裝飾器的使用方法,我們一步步來理解其定義過程。

          二、裝飾器是一個函數(shù)

          • 根據(jù)上文timethis裝飾器的定義,它毫無疑問是一個函數(shù)。名稱是timethis,參數(shù)是func,返回值是inner。
          • 根據(jù) sleeps = timethis(sleeps),可知參數(shù)func是被裝飾的函數(shù)sleeps。
          • 根據(jù)return inner,可知返回值inner是嵌套定義在裝飾器中的一個函數(shù)。
          綜上,裝飾器本身是一個函數(shù),參數(shù)也是函數(shù),返回值還是函數(shù)。之所以函數(shù)可以作為裝飾器的參數(shù)和返回值,是因為函數(shù)在Python中是一等對象。

          三、函數(shù)是一等對象

          編程語言中的一等對象定義為:運行時創(chuàng)建,可賦值給變量或數(shù)據(jù)結(jié)構(gòu),可作為參數(shù)傳遞,可作為返回值返回。
          Python中整數(shù)、字符串、字典類型是一等對象,具備以上四點特性,理解起來沒有任何困難。但函數(shù)作為一等對象,需要我們舉例說明。

          3.1運行時創(chuàng)建

          在Python控制臺中定義一個函數(shù)reverse,實現(xiàn)對word這個序列類型的反轉(zhuǎn)。
          >>>?def?reverse(word):
          ...?????return?word[::-1]
          ...
          >>>?reverse
          <function?reverse?at?0x027A4C40>
          >>>?reverse('hello?world!')
          '!dlrow?olleh'
          因其是在控制臺會話中定義的,符合第一條運行時創(chuàng)建的要求。

          3.2可賦值給變量或數(shù)據(jù)結(jié)構(gòu)

          可以將reverse函數(shù)賦值給另外的變量,再調(diào)用。如
          >>>?backward=reverse
          >>>?backward('hello?world!')
          '!dlrow?olleh'
          輸出結(jié)果同上。所以函數(shù)符合第二條可賦值給變量的要求。

          3.3函數(shù)作為參數(shù)傳遞

          當(dāng)使用高階函數(shù),如sorted時,高階函數(shù)的key關(guān)鍵字接受一個單參數(shù)函數(shù),對每個元素進(jìn)行迭代,依照這個key函數(shù)作為排序依據(jù)。
          cars?=?['Honda','toyota','hyundai','byd','ford','suzuki','peuguot','nissan','citroen','kia','vw','gm','audi','bmw','beniz']
          print(sorted(cars,key=reverse))
          輸出
          ['Honda',?'kia',?'toyota',?'ford',?'byd',?'hyundai',?'audi',?'suzuki',?'gm',?'nissan',?'citroen',?'peuguot',?'bmw',?'vw',?'beniz']
          此時所有的car是依照結(jié)尾字符的先后排序的。reverse作為參數(shù)傳入高階函數(shù)。符合第三條函數(shù)可作為參數(shù)傳遞。

          3.4函數(shù)作為返回值返回

          為驗證第四點,我們將reverse函數(shù)包裝起來,讓他在一個函數(shù)中返回。
          def?cmpLib():
          ????def?reverse(word):
          ????????return?word[::-1]
          ????return?reverse
          我們?nèi)杂蒙侠信判蚝瘮?shù),key參數(shù)必須為一個單參函數(shù)。而函數(shù)backward的執(zhí)行結(jié)果是一個函數(shù),所以我們把它的調(diào)用結(jié)果作為key值。
          print(sorted(cars,key=cmpLib())
          結(jié)果
          ['Honda',?'kia',?'toyota',?'ford',?'byd',?'hyundai',?'audi',?'suzuki',?'gm',?'nissan',?'citroen',?'peuguot',?'bmw',?'vw',?'beniz']
          可見,結(jié)果正確。所以第四條函數(shù)可作為結(jié)果返回也成立。
          綜上,函數(shù)是一等對象。除了可調(diào)用性之外,函數(shù)和其他如字典、字符串、列表對象并沒有本質(zhì)區(qū)別。
          理解裝飾器我們需要的是函數(shù)一等性定義的后三點:函數(shù)可賦值,可作參數(shù),可作返回結(jié)果。
          我們再來分析與@timethis等價的sleeps = timethis(sleeps)語句:右側(cè)函數(shù)先調(diào)用。timethis是裝飾器函數(shù),被裝飾函數(shù)sleeps作為參數(shù)傳入裝飾器中;返回結(jié)果是裝飾器中定義的inner函數(shù);右側(cè)計算結(jié)果重新賦值給變量sleeps。完美符合以上三點。也就是說sleeps函數(shù)實際上已經(jīng)指向inner函數(shù)了。
          理解了函數(shù)一等性,就理解了函數(shù)可以作為參數(shù)傳遞和作為結(jié)果返回。那么新定義的內(nèi)部函數(shù)inner為什么采用def inner(*args,**kwargs):的參數(shù)命名形式呢?

          四、可接受任意數(shù)量參數(shù)的函數(shù)

          當(dāng)我們定義不特定數(shù)量參數(shù)的函數(shù)時,可使用*開頭的參數(shù)作可接受任意數(shù)量位置參數(shù)的參數(shù),此時該參數(shù)作為一個元組使用。
          同理,可以使用**開頭的關(guān)鍵字參數(shù)接受任意數(shù)量的關(guān)鍵詞參數(shù),此時該參數(shù)作為一個字典使用。
          如果同時接受任意數(shù)量的位置參數(shù)和關(guān)鍵字參數(shù),那么只要聯(lián)合使用 * 和 ** 就可以。而 def inner(*args,**kwargs): 是約定俗成的固定寫法。來看個例子就可以理解這種寫法了。
          def?star(*args,**kwargs):
          ????print(args,kwargs)

          star(1,2,3)
          star(4,5,name='zhang')
          star(7,name='lisi',gender='m')

          輸出結(jié)果:

          (1,?2,?3)?{}
          (4,?5)?{'name':?'zhang'}
          (7,)?{'name':?'lisi',?'gender':?'m'}
          args搜集所有位置參數(shù),kwargs搜集所有關(guān)鍵字參數(shù)。這個技術(shù)應(yīng)用在inner函數(shù)上,恰如其分:當(dāng)我們調(diào)用@語法時,只有被裝飾函數(shù)sleeps作為func參數(shù)傳入timethis裝飾器中,sleeps的參數(shù)并沒有傳入裝飾器函數(shù)中。裝飾器不知道sleeps函數(shù)的參數(shù)數(shù)量和具體值,若在其中func調(diào)用參數(shù),則相當(dāng)于調(diào)用不特定名稱和數(shù)量的參數(shù)。
          接受任意參數(shù)的inner函數(shù),進(jìn)一步將參數(shù)傳給在其中執(zhí)行的func函數(shù)。func函數(shù)是被裝飾的原函數(shù)sleeps,傳給inner函數(shù)的*args,**kwargs參數(shù),直接傳遞給了被裝飾函數(shù)func。這樣就實現(xiàn)了func(*args,**kwargs)相當(dāng)于sleeps(3)的效果。
          在完成調(diào)用原函數(shù)的基礎(chǔ)上,如何添加計時功能的呢?

          五、增強被裝飾函數(shù)的行為

          以下語句實現(xiàn)了統(tǒng)計函數(shù)執(zhí)行時間的功能 ,當(dāng)然也可以實現(xiàn)比如日志記錄,建立撤銷環(huán)境之類的功能,大同小異。
          print('start?timer:')
          start?=?time.time()
          result?=?func(*args,**kwargs)?
          end?=?time.time()
          print('end?timer:%fs.'%(end?-?start))
          很簡單,就是在調(diào)用原函數(shù)的語句result = func(*args,**kwargs)前后,包裹上相應(yīng)的計時功能。
          此處func參數(shù)得以在inner內(nèi)部訪問到,還牽涉到一個不太好理解的話題——閉包,而理解閉包需要先弄清python中變量的作用域規(guī)則。

          六、變量作用域

          Python變量分全局變量,局部變量。另外函數(shù)的參數(shù)是函數(shù)的局部變量。編寫如下代碼:
          b=3
          def?func(a):
          ????print(a)
          ????print(b)
          ????b=2

          func(2)

          讓我們猜猜運行結(jié)果,應(yīng)該是1,3對吧,但執(zhí)行卻提示出錯:

          File?"dec.py",?line?47,?in?func
          ????print(b)
          UnboundLocalError:?local?variable?'b'?referenced?before?assignment
          提示先用但未賦值。但b是全局變量,一般理解不論是print(b)對全局變量的讀取,還是b=2對全局變量的賦值,都不會出現(xiàn)這個問題。
          問題出在b=2語句上,Python對在函數(shù)定義體中 賦值的變量都認(rèn)為是局部變量。從而導(dǎo)致局部變量b未賦值先使用的問題。
          為解決這個問題,若在函數(shù)中重新賦值了全局變量,需要在函數(shù)中使用global聲明其為全局變量。即函數(shù)若讀取全局變量,可以直接使用。但若在函數(shù)體中重新賦值全局變量,那就需要global聲明變量是全局變量。
          新問題出現(xiàn)了:在裝飾器timethis中,func是其參數(shù),也就是局部變量,這是無疑的。那么在inner函數(shù)中是怎么訪問func的呢?這就牽涉到閉包問題了。

          七、閉包

          閉包指延伸作用域的函數(shù),函訪可訪問定義體之外定義的非全局變量。在例子中,timethis的參數(shù)func就未定義在inner函數(shù)中,而且也不是全局變量。是閉包將其延伸到了inner函數(shù)中,作為自由變量來使用。所以閉包是一種函數(shù),保留了它在定義時存在的自由變量。本例中,閉包從timethis定義行到return inner這個范圍,此時的局部變量func對于閉包中的inner函數(shù)來說,就是自由變量,可以讀取和使用。但不可在其中對自由變量賦值。
          類似于全局變量,當(dāng)我們在嵌套的函數(shù)中對自由變量訪問時,可以自由使用。但是當(dāng)我們重新對其賦值時,解釋器會把這個值視為一個局部變量。若需賦值全局變量,需引入global聲明全局變量;若需賦值自由變量,需引入nonlocal聲明自由變量。
          因此,func是作為自由變量被閉包函數(shù)inner使用的。那么以上語句之后為什么有兩個返回語句呢?

          八、返回值和返回函數(shù)

          • 第一個返回值,返回的是func的執(zhí)行結(jié)果,它屬于inner函數(shù)的返回值,等效于sleeps函數(shù)的返回值,這是sleep函數(shù)的應(yīng)有之意。保持了原函數(shù)sleeps對外結(jié)構(gòu)的一致性。
          • 第二個返回值是inner函數(shù)本身,也就是第三部分講述的函數(shù)作為返回結(jié)果的用法。依據(jù)slepps=timethi(sleeps)語法,其返回結(jié)果是inner函數(shù),傳遞給sleeps函數(shù),使sleeps函數(shù)實際上等同于inner函數(shù)。所以調(diào)用sleeps(3)相當(dāng)于調(diào)用inner(3)。再加上圍繞他的計時功能,故而無損增加了計時功能。
          綜上,理解裝飾器最重要的是將@ 語句和賦值語句等同起來。同時需要理解被裝飾函數(shù)作為參數(shù)傳入裝飾器,嵌套函數(shù)對其進(jìn)行改造,最后作為函數(shù)返回,使被裝飾函數(shù)實質(zhì)上關(guān)聯(lián)到新函數(shù)上。


          掃碼回復(fù):2021

          獲取最新學(xué)習(xí)資源

          長按掃碼關(guān)注,一起學(xué)Python

          學(xué)習(xí)更多:
          整理了我開始分享學(xué)習(xí)筆記到現(xiàn)在超過250篇優(yōu)質(zhì)文章,涵蓋數(shù)據(jù)分析、爬蟲、機器學(xué)習(xí)等方面,別再說不知道該從哪開始,實戰(zhàn)哪里找了

          干貨文章,點個”不過分吧?



          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  内射黄色大片 | 亚洲欧洲在线观看高清 | 狠狠狠狠狠狠狠狠狠 | 91九色在线观看视频 | 午夜影院无码 |