日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Python異常調(diào)用棧

系統(tǒng) 2016 0

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

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

            
              # -*- 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()

            
          

運行時會打印異常調(diào)用棧為:

            
              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在異常對象內(nèi)添加了 __traceback__ 屬性,用于存放異常棧信息,并且在重拋出的時候自動追加當(dāng)前調(diào)用棧。在Python 2中,相同的寫法會輸出下面的內(nèi)容:

            
              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!
              
            
          

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

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

后來某一天突然在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語句竟然還能有第二個和第三個參數(shù)?把上面那段文字讀完之后,我把 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]
            
          

然后,異常調(diào)用棧打印出來就變成了:

            
              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會被自動計算,并被設(shè)置為當(dāng)前的異常類型,異常棧就是當(dāng)前的調(diào)用棧;第二種是 raise 異常類型, 異常值 或者 raise 異常類型, 一個tuple,前者表明了當(dāng)前異常的類型和值,后者表明了異常類型,傳入的tuple將被作為參數(shù)傳入對應(yīng)類型的__init__函數(shù)用于構(gòu)造一個異常對象。這種寫法是過時的,并且不再被推薦使用。第三種是 raise 異常類型,異常值或者tuple,調(diào)用棧,也就是這里用到的這種寫法。新增的第三個參數(shù)可以傳入一個調(diào)用棧,此時raise引發(fā)異常的異常棧將自動添加到調(diào)用棧上,于是就形成了我們想要的非常直觀的調(diào)用鏈顯示。

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

在Python 3中,若想實現(xiàn)類似的效果,可使用異常對象的 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!
              
            
          

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

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

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

            
          

此時異常棧就是預(yù)期的形式了:

            
              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一樣會把在異常處理環(huán)節(jié)中遇到的所有異常都打印出來。

?

PS:在寫Python 2 三參數(shù) raise 的時候出現(xiàn)了一個小插曲,VSCode的Flake8 Linter會將第三種寫法當(dāng)成第二種,并提示寫法已過時,忽略掉就好。下面是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
            
          

更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

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

【本文對您有幫助就好】

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

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 两当县| 稷山县| 朝阳市| 富阳市| 灵台县| 江川县| 庆云县| 长沙县| 桂东县| 嘉禾县| 北宁市| 和顺县| 旬阳县| 商丘市| 新晃| 肥东县| 吉隆县| 遵义县| 曲麻莱县| 清原| 砀山县| 永春县| 建水县| 桦南县| 枣阳市| 莎车县| 吴忠市| 连州市| 定兴县| 缙云县| 汉阴县| 龙口市| 广河县| 根河市| 海城市| 革吉县| 萝北县| 宿迁市| 兰西县| 清流县| 武清区|