轮子|Python2异常链

作者: 猴子007 | 来源:发表于2017-12-11 09:26 被阅读72次

介绍一个自己造的轮子,Python2异常链。

需求

习惯了用java撸码,虽说胶水代码多,但能比较好的用代码表达思路;而Python则简洁到了简陋的地步——各种鸡肋的语法糖,各种不完善的机制。比如错误处理。

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

不考虑代码风格,是标准的Python2异常处理方式:分别捕获异常,再统一成一个异常,只有msg不同,重新抛出。这种写法又丑又冗余,顶多可以改成这样:

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

即便如此,也无法解决一个最严重的问题:明明是11行抛出的异常,但打印出来的异常栈却只能追踪到22行重新抛出异常的raise语句。重点在于没有记录cause,使我们追踪到22行之后,不知道为什么会抛出cause,也就无法定位到实际发生问题的代码。

异常链

最理想的方式,还是在异常栈中打印异常链:

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

就像Java的异常栈,区分“要抛出的异常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导致;1行与9行下是各异常的调用路径trace。不熟悉Java异常栈的可参考你真的会阅读Java的异常信息吗?

轮子

调研让我们拒绝重复造轮子

Python3已经支持了异常链,通过from关键字即可记录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

有知道正确姿势的求点破。

没找到重复轮子真是极好的

非常简单:

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作为参数。用法如下:

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 国际许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名及链接。

相关文章

  • 轮子|Python2异常链

    介绍一个自己造的轮子,Python2异常链。 需求 习惯了用java撸码,虽说胶水代码多,但能比较好的用代码表达思...

  • 异常链

    当一个异常中包含了另一个异常结果会如何!!! 最终程序抛出的是子异常,就是被包含的那个异常,这样的话在审查问题的时...

  • 2018-10-08 Python21 异常、time模块

    异常处理-基本功能: Python3用元祖接收多个异常,Python2不用写圆括号。Python3用基类捕获所有异...

  • 转自 segmentfault<python编码的意义&g

    原文见python编码的意义,感谢jiminhuang大神 编码,还是编码 python2的直钩——编码异常 当你...

  • 教你手写全局异常拦截器

    为什么要重复造轮子 你可能会问,Spring已经自带了全局异常拦截,为什么还要重复造轮子呢? 这是个好问题,我觉得...

  • python编码的意义

    原文出处: jiminhuang python2的直钩——编码异常 当你用python打开一篇中文文档,准备读取里...

  • 一种可以取代区块链技术的新架构

    为啥要发明新的轮子 区块链技术这几年实在是太火了,大家都在学习和讨论区块链。但是区块链技术目前正在成功的应用只有比...

  • 运维融合可视化平台介绍

    服务链监控大屏 服务链监控大屏可以快捷明了的看到异常资产的情况,数据链的异常数量与资产分布类型。 左上方的资产表格...

  • 异常处理

    JAVA严格的异常处理机制和类型检查机制 异常处理手贱异常非手贱异常 异常链处理 异常是在程序出错的时候能知道程序...

  • java中的异常链

    1.自定义新的异常 2.测试异常链 3.运行结果

网友评论

    本文标题:轮子|Python2异常链

    本文链接:https://www.haomeiwen.com/subject/gukhixtx.html