Python 入門之多個裝飾器執行順序

2021-03-02 51reboot運維開發

裝飾器是 Python 用於封裝函數或代碼的工具,網上可以搜到很多文章可以學習,在這裡要討論的是多個裝飾器執行順序的一個迷思。

大部分涉及多個裝飾器裝飾的函數調用順序時都會說明它們是自上而下的,比如下面這個例子:

def decorator_a(func):
   print 'Get in decorator_a'
   def inner_a(*args, **kwargs):
       print 'Get in inner_a'
       return func(*args, **kwargs)
   return inner_a

def decorator_b(func):
   print 'Get in decorator_b'
   def inner_b(*args, **kwargs):
       print 'Get in inner_b'
       return func(*args, **kwargs)
   return inner_b

@decorator_b
@decorator_a
def f(x):
   print 'Get in f'
   return x * 2

f(1)

上面代碼先定義裡兩個函數: decotator_a, decotator_b, 這兩個函數實現的功能是,接收一個函數作為參數然後返回創建的另一個函數,在這個創建的函數裡調用接收的函數(文字比代碼繞人)。最後定義的函數 f 採用上面定義的 decotator_a, decotator_b 作為裝飾函數。在當我們以1為參數調用裝飾後的函數 f 後, decotator_a, decotator_b 的順序是什麼呢(這裡為了表示函數執行的先後順序,採用列印輸出的方式來查看函數的執行順序)?

如果不假思索根據自下而上的原則來判斷地話,先執行 decorator_a 再執行 decorator_b , 那麼會先輸出 Get in decotator_a, Get in inner_a 再輸出 Get in decotator_b , Get in inner_b 。然而事實並非如此。實際上運行的結果如下:

Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f

為什麼是先執行 inner_b 再執行 inner_a 呢?為了徹底看清上面的問題,得先分清兩個概念:函數和函數調用。上面的例子中 f 稱之為函數, f(1) 稱之為函數調用,後者是對前者傳入參數進行求值的結果。在Python中函數也是一個對象,所以 f 是指代一個函數對象,它的值是函數本身, f(1) 是對函數的調用,它的值是調用的結果,這裡的定義下 f(1) 的值2。同樣地,拿上面的 decorator_a 函數來說,它返回的是個函數對象 inner_a ,這個函數對象是它內部定義的。在 inner_a 裡調用了函數 func ,將 func 的調用結果作為值返回。

其次得理清的一個問題是,當裝飾器裝飾一個函數時,究竟發生了什麼。現在簡化我們的例子,假設是下面這樣的:

def decorator_a(func):
   print 'Get in decorator_a'
   def inner_a(*args, **kwargs):
       print 'Get in inner_a'
       return func(*args, **kwargs)
   return inner_a

@decorator_a
def f(x):
   print 'Get in f'
   return x * 2

正如很多介紹裝飾器的文章裡所說:

@decorator_a
def f(x):
   print 'Get in f'
   return x * 2


def f(x):
   print 'Get in f'
   return x * 2

f = decorator_a(f)

所以,當解釋器執行這段代碼時, decorator_a 已經調用了,它以函數 f 作為參數, 返回它內部生成的一個函數,所以此後 f 指代的是 decorater_a 裡面返回的 inner_a 。所以當以後調用 f 時,實際上相當於調用 inner_a ,傳給 f 的參數會傳給 inner_a , 在調用 inner_a 時會把接收到的參數傳給 inner_a 裡的 func 即 f ,最後返回的是 f 調用的值,所以在最外面看起來就像直接再調用 f 一樣。

當理清上面兩方面概念時,就可以清楚地看清最原始的例子中發生了什麼。

當解釋器執行下面這段代碼時,實際上按照從下到上的順序已經依次調用了 decorator_a 和 decorator_b ,這是會輸出對應的 Get in decorator_a 和 Get in decorator_b 。 這時候 f 已經相當於 decorator_b 裡的 inner_b 。但因為 f 並沒有被調用,所以 inner_b 並沒有調用,依次類推 inner_b 內部的 inner_a 也沒有調用,所以 Get in inner_a 和 Get in inner_b 也不會被輸出。

@decorator_b
@decorator_a
def f(x):
   print 'Get in f'
   return x * 2

然後最後一行當我們對 f 傳入參數1進行調用時, inner_b 被調用了,它會先列印 Get in inner_b ,然後在 inner_b 內部調用了 inner_a 所以會再列印 Get in inner_a, 然後再 inner_a 內部調用的原來的 f, 並且將結果作為最終的返回。這時候你該知道為什麼輸出結果會是那樣,以及對裝飾器執行順序實際發生了什麼有一定了解了吧。

當我們在上面的例子最後一行 f 的調用去掉,放到repl裡演示,也能很自然地看出順序問題:

➜  test git:(master) ✗ python
Python 2.7.11 (default, Jan 22 2016, 08:29:18)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import test13
Get in decorator_a
Get in decorator_b
>>> test13.f(1)
Get in inner_b
Get in inner_a
Get in f
2
>>> test13.f(2)
Get in inner_b
Get in inner_a
Get in f
4
>>>

在實際應用的場景中,當我們採用上面的方式寫了兩個裝飾方法比如先驗證有沒有登錄 @login_required , 再驗證權限夠不夠時 @permision_allowed 時,我們採用下面的順序來裝飾函數:

@login_required
@permision_allowed
def f()
 # Do something
 return

相關焦點

  • Selenium2+python自動化55-unittest之裝飾器(@classmethod)
    於是就想是不是可以只打開一次瀏覽器,執行完用例再關閉呢?這就需要用到裝飾器(@classmethod)來解決了。 一、裝飾器1.用setUp與setUpClass區別setup():每個測試case運行前運行teardown():每個測試case運行完後執行setUpClass():必須使用@classmethod 裝飾器,所有case運行前只運行一次tearDownClass():必須使用@classmethod裝飾器,所有case
  • Python 的lru_cache裝飾器使用簡介
    從輸出結果中可以看出,存在著嚴重的重複計算情況,比如 fibonacci(1) 就被計算了 5 次之多。這還只是計算 6 次的 fibonacci 函數。2 優化上面的示例代碼加入 lru_cache 裝飾器:
  • 第57p,裝飾器魔法糖,多個裝飾器的使用
    大家好,我是楊數Tos,這是《從零基礎到大神》系列課程的第X篇文章,第三階段的課程:Python進階知識:Python進階知識:詳細講解Python中的函數(十)====> 函數的嵌套之裝飾器詳解(下篇)。
  • 一文讀懂Python裝飾器
    打開APP 一文讀懂Python裝飾器 工程師3 發表於 2018-04-28 10:48:00 先來看一個簡單例子,雖然實際代碼可能比這複雜很多: 現在有一個新的需求,希望可以記錄下函數的執行日誌
  • Python遞歸函數、閉包和裝飾器
    >sys.setrecursionlimit(500)sys.getrecursionlimit()500二、閉包 closure閉包定義:將內嵌函數的語句和這些語句的執行環境打包在一起時decorators(專業提高篇)裝飾器的定義裝飾器是程序開發中經常會用到的一個功能,用來在原有的函數上增添新的代碼需求。
  • Python退避及重試裝飾器:backoff
    這裡有一個例子,當出現任何requests異常時,使用指數退避(即退避時間指數增長):對於需要多個異常類型採取相同退避行為的情況,裝飾器還將接受異常元組:使用多個裝飾器還可以組合退避裝飾器以指定不同情況下的不同退避行為:運行時配置裝飾函數on_exception和on_predicate通常在導入時確定參數。
  • Python裝飾器以及高級用法
    用例:計時函數執行假設我們正在執行一段代碼,執行時間比我們想的還要長一些。這段代碼由一堆函數調用組成,我們確信這些調用中至少有一個調用構成了我們代碼中的瓶頸。我們如何找到瓶頸?現在有一個解決方案,就是我們現在要關注的解決方案,就是對函數執行進行計時。讓我們從一個簡單的例子開始。
  • 詳解Python的裝飾器
    format(something)至此,你已完全掌握初級的裝飾器寫法。高級一點的裝飾器帶參數的裝飾器和類裝飾器屬於進階的內容。在理解這些裝飾器之前,最好對函數的閉包和裝飾器的接口約定有一定了解。(參見https://betacat.online/posts/2016-10-23/python-closure/)帶參數的裝飾器假設我們前文的裝飾器需要完成的功能不僅僅是能在進入某個函數後打出log信息,而且還需指定log的級別,那麼裝飾器就會是這樣的。
  • 使用python強大的裝飾器,監控數據自動化流程日誌
    裝飾器是python當中的進階用法,並且應用非常廣泛,但是在實際的工作過程中,我發現很多人常常因為覺得裝飾器很複雜,望而生畏。實際上從原理來講並不難。,所以最終當執行send_message的時候,就相當於執行了func()這個函數。
  • Python中unittest用法實例
    具體用法分析如下:1. unittest module包含了編寫運行unittest的功能,自定義的test class都要集成unitest.TestCase類,test method要以test開頭,運行順序根據test method的名字排序,特殊方法:① setup():每個測試函數運行前運行② teardown():每個測試函數運行完後執行③ setUpClass
  • 人人都能看懂的 Python 裝飾器入門教程!
    本文我將嘗試說清楚為什麼需要現裝飾器、什麼是裝飾器、以及如何寫一個簡單的裝飾器,但要徹底理解裝飾器還要從函數開始說起,下面是有關函數的四個重要的概念,希望大家可以明白。用@+裝飾器函數名字放在需要被裝飾函數的上方即可,現在直接調用add即可實現裝飾器的功能!
  • 詳解python三大器——迭代器、生成器、裝飾器
    在python中如果一個對象實現了 __iter__方法,我們就稱之為可迭代對象,可以查看set\list\tuple…等源碼內部均實現了__iter__方法如果一個對象未實現__iter__方法,但是對其使用for…in則會拋出TypeError: 『xxx』 object is not iterable    可以通過isinstance(obj
  • Python unittest單元測試框架的使用
    setUp():準備環境,執行每個測試用例的前置條件;tearDown():環境還原,執行每個測試用例的後置條件;setUpClass():必須使用@classmethod裝飾器,所有case執行的前置條件,只運行一次;tearDownClass():必須使用@classmethod裝飾器,所有case運行完後只運行一次;例如:
  • 如何入門Python之Python基礎教程詳解
    隨著人工智慧的發展,Python近兩年也是大火,越來越多的人加入到Python學習大軍,對於毫無基礎的人該如何入門Python呢?這裡整理了一些個人經驗和Python入門教程供大家參考。如果你是零基礎入門 Python 的話,建議初學者至少達到兩個目標: 會用,理解。
  • Python趣味打怪:147段簡單代碼完成從入門到大師
    入門簡單如十進位轉二進位,盡顯Python簡潔之美:In [1]: bin(10)Out[1]: '0b1010'冬天到了,就算沒有點亮手繪技能,也能用簡單幾行代碼繪出漫天雪花:例子是有趣的例子,教程也是正經教程,學習路徑清晰、系統,先一起來看看完整目錄:
  • 有趣的Python上下文管理器
    下面就編寫一個名為RunTime的裝飾器,該裝飾器將能打開文件。上下文管理器擁有類似堆棧的邏輯,應按相反的順序退出。任何一個上下文管理器都可以處理異常,若此異常已經被某個管理器所處理,那麼其它的管理器將不會收到有關此異常的任何信息;因此,如果發生異常,諸多嵌套的上下文管理器的邏輯順序就成了運維的重要依據;而問題的另一方面,我們可以使用__exit__方法來引發異常,然後使用上下文管理器對其進行處理。
  • python編程入門,零基礎學習Python基礎教程
    這裡推薦這門python編程入門基礎教程,適合零基礎的同學學習!python軟體工程師都學什麼?自學Python,看視頻學的更快、更透徹一些,給你個課程大綱!階段一:Python開發基礎Python全棧開發與人工智慧之Python開發基礎知識學習內容包括:Python基礎語法、數據類型、字符編碼、文件操作、函數、裝飾器、迭代器、內置方法、常用模塊等。
  • 一文讀懂python裝飾器由來(二)
    --  Illustrations by Charlie Davis  --上一篇文章主要以一步一步演進的方式介紹了裝飾器的工作原理以及使用
  • Python中asyncio神器的入門
    協程實際上是在一個線程中,只不過每個協程對CPU進行順序分時使用。任務執行順序用戶可控。線程,多線程共享進程中的資源,通過全局GIL鎖來平均分配CPU,在不同線程中不斷切換執行任務,任務執行順序用戶不能控制。asyncioasyncio是Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。
  • Python 測試框架unittest和pytest的優劣
    本次示例中使用的為python2.7。二、PytestPytest是Python的另一個第三方單元測試庫。它的目的是讓單元測試變得更容易,並且也能擴展到支持應用層面複雜的功能測試。pytest的特性有:支持用簡單的assert語句實現豐富的斷言,無需複雜的self.assert*函數自動識別測試模塊和測試函數模塊化夾具用以管理各類測試資源對 unittest 完全兼容,對 nose基本兼容支持Python3和PyPy3豐富的插件生態,已有300多個各式各樣的插件,社區繁榮