欧美三区_成人在线免费观看视频_欧美极品少妇xxxxⅹ免费视频_a级毛片免费播放_鲁一鲁中文字幕久久_亚洲一级特黄

Python異常調用棧

系統 1867 0

一般來說,當異常發生時,其異常棧應該從主調用者的入口一直到異常發生點,例如Java里經常出現的長達一兩頁的stack trace,這其中可能存在中間層代碼收到異常時,進行一些動作(關閉數據庫連接或者文件等),然后再次拋出異常的情況。

Python 3中,在except塊內進行處理,然后重新拋出異常即可,例如下面的測試代碼:

            
              # -*- coding: utf-8 -*-
import sys


def a():
    b()


def b():
    c()  # call the c


def c():
    raise Exception("Hello World!")


class MyException(Exception):
    pass


def m_a():
    m_b()


def m_b():
    m_c()  # call the m_c


def m_c():
    try:
        a()
    except Exception as e:
        raise e


m_a()

            
          

運行時會打印異常調用棧為:

            
              Traceback (most recent call last):
  File "test.py", line 36, in 
              
                
    m_a()
  File "test.py", line 22, in m_a
    m_b()
  File "test.py", line 26, in m_b
    m_c()  # call the m_c
  File "test.py", line 33, in m_c
    raise e
  File "test.py", line 31, in m_c
    a()
  File "test.py", line 6, in a
    b()
  File "test.py", line 10, in b
    c()  # call the c
  File "test.py", line 14, in c
    raise Exception("Hello World!")
Exception: Hello World!
              
            
          

這是因為Python 3在異常對象內添加了 __traceback__ 屬性,用于存放異常棧信息,并且在重拋出的時候自動追加當前調用棧。在Python 2中,相同的寫法會輸出下面的內容:

            
              Traceback (most recent call last):
  File "exception_test.py", line 37, in 
              
                
    m_a()
  File "exception_test.py", line 22, in m_a
    m_b()
  File "exception_test.py", line 26, in m_b
    m_c()  # call the m_c
  File "exception_test.py", line 34, in m_c
    raise e
Exception: Hello World!
              
            
          

可以看到,打印出來的異常棧內容只能追蹤到 m_c 函數的 raise 語句,而引起這個異常的原因丟失掉了。

為了讓Python 2也能顯示像Python 3一樣的異常棧,我曾經嘗試過使用某個包裝類將當前異常和棧保存起來,然后在Top-Level通過操作字符串來拼湊成一個完整的異常棧,但實現上不是很優雅,而且要求代碼必須使用這個異常包裝類才行。

后來某一天突然在Python官方文檔上看見raise語句的解釋:

6.9. The? raise ?statement

            
              raise_stmt
            
             ::=  "raise" [
            
              expression
            
             ["," 
            
              expression
            
             ["," 
            
              expression
            
            ]]]

          

If no expressions are present,? raise ?re-raises the last exception that was active in the current scope. If no exception is active in the current scope, a? TypeError ?exception is raised indicating that this is an error (if running under IDLE, a? Queue.Empty ?exception is raised instead).

Otherwise,? raise ?evaluates the expressions to get three objects, using? None ?as the value of omitted expressions. The first two objects are used to determine the? type ?and? value ?of the exception.

If the first object is an instance, the type of the exception is the class of the instance, the instance itself is the value, and the second object must be? None .

If the first object is a class, it becomes the type of the exception. The second object is used to determine the exception value: If it is an instance of the class, the instance becomes the exception value. If the second object is a tuple, it is used as the argument list for the class constructor; if it is? None , an empty argument list is used, and any other object is treated as a single argument to the constructor. The instance so created by calling the constructor is used as the exception value.

If a third object is present and not? None , it must be a traceback object (see section?The standard type hierarchy), and it is substituted instead of the current location as the place where the exception occurred. If the third object is present and not a traceback object or? None , a? TypeError ?exception is raised. The three-expression form of? raise ?is useful to re-raise an exception transparently in an except clause, but? raise ?with no expressions should be preferred if the exception to be re-raised was the most recently active exception in the current scope.

Additional information on exceptions can be found in section?Exceptions, and information about handling exceptions is in section?The try statement.

什么?raise語句竟然還能有第二個和第三個參數?把上面那段文字讀完之后,我把 raise e 改成了 raise Exception, e, sys.exc_info()[2]

            
              def m_c():
    try:
        a()
    except Exception as e:
        raise Exception, e, sys.exc_info()[2]
            
          

然后,異常調用棧打印出來就變成了:

            
              Traceback (most recent call last):
  File "exception_test.py", line 37, in 
              
                
    m_a()
  File "exception_test.py", line 22, in m_a
    m_b()
  File "exception_test.py", line 26, in m_b
    m_c()  # call the m_c
  File "exception_test.py", line 31, in m_c
    a()
  File "exception_test.py", line 6, in a
    b()
  File "exception_test.py", line 10, in b
    c()  # call the c
  File "exception_test.py", line 14, in c
    raise Exception("Hello World!")
Exception: Hello World!
              
            
          

其實就是文檔中的寫法,raise語句允許三種形式:第一種就是最常用的直接raise一個異常對象,此時異常對象的type會被自動計算,并被設置為當前的異常類型,異常棧就是當前的調用棧;第二種是 raise 異常類型, 異常值 或者 raise 異常類型, 一個tuple,前者表明了當前異常的類型和值,后者表明了異常類型,傳入的tuple將被作為參數傳入對應類型的__init__函數用于構造一個異常對象。這種寫法是過時的,并且不再被推薦使用。第三種是 raise 異常類型,異常值或者tuple,調用棧,也就是這里用到的這種寫法。新增的第三個參數可以傳入一個調用棧,此時raise引發異常的異常棧將自動添加到調用棧上,于是就形成了我們想要的非常直觀的調用鏈顯示。

另外,raise還可以不接任何參數,此時raise將拋出當前上下文存在的異常,如果當前不在異常處理上下文中,TypeError會被拋出。也就是說這里直接寫 raise 也是一樣的效果。不過使用帶調用棧的raise語句有一個好處:包裝異常并統一異常類型。假設下層代碼會拋出多種異常,此時作為模塊作者希望讓調用者看到異常發生的具體地點,但又不希望調用者except每種異常類型,此時可以定義一個 class MyException,然后在重拋出異常的時候寫 raise MyException, 參數, sys.exc_info()[2] 。此時調用者會得到完整的異常棧,同時異常類型變為了MyException。

在Python 3中,若想實現類似的效果,可使用異常對象的 with_traceback 方法,例如將 m_c 方法改寫成:

            
              def m_c():
    try:
        a()
    except Exception as e:
        raise MyException(e).with_traceback(e.__traceback__)
            
          

此時Top-Level打印的異常棧為:

            
              Traceback (most recent call last):
  File "test.py", line 31, in m_c
    a()
  File "test.py", line 6, in a
    b()
  File "test.py", line 10, in b
    c()  # call the c
  File "test.py", line 14, in c
    raise Exception("Hello World!")
Exception: Hello World!

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 38, in 
              
                
    m_a()
  File "test.py", line 22, in m_a
    m_b()
  File "test.py", line 26, in m_b
    m_c()  # call the m_c
  File "test.py", line 34, in m_c
    raise MyException(e).with_traceback(e.__traceback__)
  File "test.py", line 31, in m_c
    a()
  File "test.py", line 6, in a
    b()
  File "test.py", line 10, in b
    c()  # call the c
  File "test.py", line 14, in c
    raise Exception("Hello World!")
__main__.MyException: Hello World!
              
            
          

可以看到,一共打印了兩個異常,最后打印的異常附帶了完成的異常調用棧信息,但是多了一條重拋出語句。

為了不向外界展示異常重拋出流程,可以直接改寫 __traceback__ 屬性,如下:

            
              def m_c():
    try:
        a()
    except Exception as e:
        e.__traceback__ = sys.exc_info()[2]
        raise

            
          

此時異常棧就是預期的形式了:

            
              Traceback (most recent call last):
  File "test.py", line 41, in 
              
                
    m_a()
  File "test.py", line 22, in m_a
    m_b()
  File "test.py", line 26, in m_b
    m_c()  # call the m_c
  File "test.py", line 31, in m_c
    a()
  File "test.py", line 6, in a
    b()
  File "test.py", line 10, in b
    c()  # call the c
  File "test.py", line 14, in c
    raise Exception("Hello World!")
Exception: Hello World!
              
            
          

Python 3還提供了raise ... from 的語法,但我個人感覺這個功能不是那么實用... 如果將異常語句改寫成 raise MyException(e) from e,會得到下面的異常棧:

            
              Traceback (most recent call last):
  File "test.py", line 31, in m_c
    a()
  File "test.py", line 6, in a
    b()
  File "test.py", line 10, in b
    c()  # call the c
  File "test.py", line 14, in c
    raise Exception("Hello World!")
Exception: Hello World!

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 38, in 
              
                
    m_a()
  File "test.py", line 22, in m_a
    m_b()
  File "test.py", line 26, in m_b
    m_c()  # call the m_c
  File "test.py", line 33, in m_c
    raise MyException(e) from e
__main__.MyException: Hello World!
              
            
          

雖然兩個異常都顯示出來了,但是沒有連接在一起,并不是很直觀,對debug幫助沒有那么大。而且就算不寫 from 子句,Python 3一樣會把在異常處理環節中遇到的所有異常都打印出來。

?

PS:在寫Python 2 三參數 raise 的時候出現了一個小插曲,VSCode的Flake8 Linter會將第三種寫法當成第二種,并提示寫法已過時,忽略掉就好。下面是Flake8 Rules給出的W602警告解釋:

Deprecated form of raising exception (W602)

The? raise Exception, message ?form of raising exceptions is deprecated. Use the new form.

Anti-pattern

            
              def can_drive(age):
    if age < 16:
        raise ValueError, 'Not old enough to drive'
    return True

            
          

Best practice

            
              def can_drive(age):
    if age < 16:
        raise ValueError('Not old enough to drive')
    return True
            
          

更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 免费一级特黄3大片视频 | 毛片入口| 91精品国产闺蜜国产在线 | 国产福利观看 | 一级黄色大片视频 | 香港三级日本三级韩国三级韩 | 久草在线免费新视频 | 日本美女一区二区 | 性爱视频在线免费 | 国产精品视频播放 | 日日夜夜综合 | 九九re6精品视频在线观看 | 青青青青手机在线视频观看国产 | 久久影城 | 欧美aⅴ片 | 浮力影院最新地址 | 欧美日韩一区二区三在线 | 欧美女人天堂 | 日韩精品久久一区二区三区 | 亚洲精品乱码久久久久久久久久 | 91观看| 欧美日韩久久久 | 色婷婷综合网 | 国产乱人乱精一区二区视频密 | 免费黄色福利 | 特级av毛片免费观看 | 欧美黄色一区 | 国产欧美日韩精品一区 | 尤物视频在线观看 | 五月天婷婷免费观看视频在线 | 国产精品成人一区二区 | 国内精品一区二区 | 欧美日韩在线看 | 国产一区| 能直接看av的网站 | 日韩视频在线一区二区三区 | 欧美精品播放 | 免费看污成人午夜网站 | 人阁色第四影院在线电影 | 色先锋av资源中文字幕 | 91看片在线看 |