輪子|Python2異常鏈

介紹一個自己造的輪子,Python2異常鏈。

需求

習(xí)慣了用java擼碼僵腺,雖說膠水代碼多,但能比較好的用代碼表達(dá)思路壶栋;而Python則簡潔到了簡陋的地步——各種雞肋的語法糖辰如,各種不完善的機(jī)制。比如錯誤處理贵试。

Python2沒有異常鏈琉兜,讓問題排查變得非常困難

# coding=utf-8
import sys


class UnexpectedError(StandardError):
    pass


def divide(division, divided):
    if division == 0:
        raise ValueError("illegal input: %s, %s" % (division, divided))
    ans = division / divided
    return ans


a = 0
b = 0
try:
    print divide(a, b)
except ValueError as e:
    # m_counter.inc("err", 1)
    raise UnexpectedError("illegal input: %s, %s" % (a, b))
except ZeroDivisionError as e:
    # m_counter.inc("err", 1)
    raise UnexpectedError("divide by zero")
except StandardError as e:
    # m_counter.inc("err", 1)
    raise UnexpectedError("other error...")

打印異常如下:

Traceback (most recent call last):
  File "/Users/monkeysayhi/PycharmProjects/Wheel/utils/tmp/tmp.py", line 22, in <module>
    raise UnexpectedError("illegal input: %s, %s" % (a, b))
__main__.UnexpectedError: illegal input: 0, 0

不考慮代碼風(fēng)格凯正,是標(biāo)準(zhǔn)的Python2異常處理方式:分別捕獲異常,再統(tǒng)一成一個異常豌蟋,只有msg不同廊散,重新拋出。這種寫法又丑又冗余梧疲,頂多可以改成這樣:

try:
    print divide(a, b)
except StandardError as e:
    # m_counter.inc("err", 1)
    raise UnexpectedError(e.message)

即便如此允睹,也無法解決一個最嚴(yán)重的問題:明明是11行拋出的異常,但打印出來的異常棧卻只能追蹤到22行重新拋出異常的raise語句幌氮。重點在于沒有記錄cause缭受,使我們追蹤到22行之后,不知道為什么會拋出cause该互,也就無法定位到實際發(fā)生問題的代碼米者。

異常鏈

最理想的方式,還是在異常棧中打印異常鏈:

try:
    print divide(a, b)
except StandardError as cause:
    # m_counter.inc("err", 1)
    raise UnexpectedError("some msg", cause)

就像Java的異常棧宇智,區(qū)分“要拋出的異常UnexpectedError和引起該異常的原因cause”:

java.lang.RuntimeException: level 2 exception
    at com.msh.demo.exceptionStack.Test.fun2(Test.java:17)
    at com.msh.demo.exceptionStack.Test.main(Test.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: java.io.IOException: level 1 exception
    at com.msh.demo.exceptionStack.Test.fun1(Test.java:10)
    at com.msh.demo.exceptionStack.Test.fun2(Test.java:15)
    ... 6 more

上述異常棧表示蔓搞,RuntimeException由IOException導(dǎo)致;1行與9行下是各異常的調(diào)用路徑trace随橘。不熟悉Java異常棧的可參考你真的會閱讀Java的異常信息嗎喂分?

輪子

調(diào)研讓我們拒絕重復(fù)造輪子

Python3已經(jīng)支持了異常鏈机蔗,通過from關(guān)鍵字即可記錄cause妻顶。

Python2 future包提供的所謂異常鏈raise_from我是完全沒明白到哪里打印了cause:

from future.utils import raise_from


class DatabaseError(Exception):
    pass


class FileDatabase:
    def __init__(self, filename):
        try:
            self.file = open(filename)
        except IOError as exc:
            raise_from(DatabaseError('failed to open'), exc)


# test
fd = FileDatabase('non_existent_file.txt')

那么,11行拋出的IOError呢蜒车??幔嗦?似乎僅僅多了一句無效信息(future包里的raise e)酿愧。

Traceback (most recent call last):
  File "/Users/mobkeysayhi/PycharmProjects/Wheel/utils/tmp/tmp.py", line 17, in <module>
    fd = FileDatabase('non_existent_file.txt')
  File "/Users/mobkeysayhi/PycharmProjects/Wheel/utils/tmp/tmp.py", line 13, in __init__
    raise_from(DatabaseError('failed to open'), exc)
  File "/Library/Python/2.7/site-packages/future/utils/__init__.py", line 454, in raise_from
    raise e
__main__.DatabaseError: failed to open

有知道正確姿勢的求點破。

沒找到重復(fù)輪子真是極好的

非常簡單:

import traceback


class TracedError(BaseException):
    def __init__(self, msg="", cause=None):
        trace_msg = msg
        if cause is not None:
            _spfile = SimpleFile()
            traceback.print_exc(file=_spfile)
            _cause_tm = _spfile.read()
            trace_msg += "\n" \
                         + "\nCaused by:\n\n" \
                         + _cause_tm
        super(TracedError, self).__init__(trace_msg)


class ErrorWrapper(TracedError):
    def __init__(self, cause):
        super(ErrorWrapper, self).__init__("Just wrapping cause", cause)


class SimpleFile(object):
    def __init__(self, ):
        super(SimpleFile, self).__init__()
        self.buffer = ""

    def write(self, str):
        self.buffer += str

    def read(self):
        return self.buffer

目前只支持單線程模型邀泉,github上有doc和測試用例嬉挡,戳我戳我

一個測試輸出如下:

Traceback (most recent call last):
  File "/Users/monkeysayhi/PycharmProjects/Wheel/utils/exception_chain/traced_error.py", line 68, in <module>
    __test()
  File "/Users/monkeysayhi/PycharmProjects/Wheel/utils/exception_chain/traced_error.py", line 64, in __test
    raise MyError("test MyError", e)
__main__.MyError: test MyError

Caused by:

Traceback (most recent call last):
  File "/Users/monkeysayhi/PycharmProjects/Wheel/utils/exception_chain/traced_error.py", line 62, in __test
    zero_division()
  File "/Users/monkeysayhi/PycharmProjects/Wheel/utils/exception_chain/traced_error.py", line 58, in zero_division
    a = 1 / 0
ZeroDivisionError: integer division or modulo by zero

另外汇恤,為方便處理后重新拋出某些異常庞钢,還提供了ErrorWrapper,僅接收一個cause作為參數(shù)因谎。用法如下:

for pid in pids:
    # process might have died before getting to this line
    # so wrap to avoid OSError: no such process
    try:
        os.kill(pid, signal.SIGKILL)
    except OSError as os_e:
        if os.path.isdir("/proc/%d" % int(pid)):
            logging.warn("Timeout but fail to kill process, still exist: %d, " % int(pid))
            raise ErrorWrapper(os_e)
        logging.debug("Timeout but no need to kill process, already no such process: %d" % int(pid))

參考:


本文鏈接:輪子|Python2異常鏈
作者:猴子007
出處:https://monkeysayhi.github.io
本文基于 知識共享署名-相同方式共享 4.0 國際許可協(xié)議發(fā)布基括,歡迎轉(zhuǎn)載,演繹或用于商業(yè)目的财岔,但是必須保留本文的署名及鏈接风皿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末河爹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子桐款,更是在濱河造成了極大的恐慌咸这,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魔眨,死亡現(xiàn)場離奇詭異媳维,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)遏暴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門侄刽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拓挥,你說我怎么就攤上這事唠梨。” “怎么了侥啤?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵当叭,是天一觀的道長。 經(jīng)常有香客問我盖灸,道長蚁鳖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任赁炎,我火速辦了婚禮醉箕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘徙垫。我一直安慰自己讥裤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布姻报。 她就那樣靜靜地躺著己英,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吴旋。 梳的紋絲不亂的頭發(fā)上损肛,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音荣瑟,去河邊找鬼治拿。 笑死,一個胖子當(dāng)著我的面吹牛笆焰,可吹牛的內(nèi)容都是我干的劫谅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼同波!你這毒婦竟也來了鳄梅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤未檩,失蹤者是張志新(化名)和其女友劉穎戴尸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冤狡,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡孙蒙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悲雳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挎峦。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖合瓢,靈堂內(nèi)的尸體忽然破棺而出坦胶,到底是詐尸還是另有隱情,我是刑警寧澤晴楔,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布顿苇,位于F島的核電站,受9級特大地震影響税弃,放射性物質(zhì)發(fā)生泄漏纪岁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一则果、第九天 我趴在偏房一處隱蔽的房頂上張望幔翰。 院中可真熱鬧,春花似錦西壮、人聲如沸遗增。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贡定。三九已至,卻和暖如春可都,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚓耽。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工渠牲, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人步悠。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓签杈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子答姥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容