东东东 陈煜东的博客

Python重新抛出异常的姿势

异常对于一个语言来说非常重要,异常的栈信息对于开发者特别重要,因为可以根据异常栈来找到第一次抛出异常的地方。但是懂得正确的抛出异常的人不是很多。

首先,以下是最糟糕的

def revert_stuff():
    pass

def some_code():
    raise Exception('oops, some error occur')

try:
    some_code()
except:
    revert_stuff()
    raise Exception("some_code failed!")
Traceback (most recent call last):
  File "1.py", line 11, in <module>
    raise Exception("some_code failed!")
Exception: some_code failed!

为什么说是最糟糕呢?因为关于some_code()的失败信息全部都丢失了。不过这些异常堆栈,可能是我们期望的信息,也可能不是。

以下代码是稍微改进过,但还是不是非常好:

def revert_stuff():
    pass

def some_code():
    raise Exception('oops, some error occur')

try:
    some_code()
except:
    import traceback
    traceback.print_exc()
    revert_stuff()
    raise Exception("some_code failed!")
Traceback (most recent call last):
  File "2.py", line 8, in <module>
    some_code()
  File "2.py", line 5, in some_code
    raise Exception('oops, some error occur')
Exception: oops, some error occur
Traceback (most recent call last):
  File "2.py", line 13, in <module>
    raise Exception("some_code failed!")
Exception: some_code failed!

使用traceback.print_exc() 把原始的异常堆栈(traceback)打印了出来。从某些角度来看这是最好的方法了,因为可以异常的错误信息找出来。但是如果不想恢复着新异常信息,那么应该这么做:

def revert_stuff():
    pass

def some_code():
    raise Exception('oops, some error occur')

try:
    some_code()
except:
    revert_stuff()
    raise
Traceback (most recent call last):
  File "3.py", line 8, in <module>
    some_code()
  File "3.py", line 5, in some_code
    raise Exception('oops, some error occur')
Exception: oops, some error occur

使用不用带任何参数raise可以重新抛出最后的异常。有时人们直接留空从来不用except:语句,但是这个特殊的形式(except: + raise)也是可以的。

有另外一种raise抛出异常的方式,知道的人比较少,但是也很容易上手。类似无参数的raise一样,这种方法也可以保留异常堆栈:

def some_code():
    raise Exception('oops, some error occur')

def maybe_raise(exc_info):
    raise exc_info[0], exc_info[1], exc_info[2]

try:
    some_code()
except:
    import sys
    exc_info = sys.exc_info()
    maybe_raise(exc_info)
Traceback (most recent call last):
  File "4.py", line 12, in <module>
    maybe_raise(exc_info)
  File "4.py", line 8, in <module>
    some_code()
  File "4.py", line 2, in some_code
    raise Exception('oops, some error occur')
Exception: oops, some error occur

如果你需要在异常发生的代码的其他地方处理异常是个不错的选择。但通常它不是很方便,因为这样比较晦涩难懂。

还有一类经常修改异常堆栈的情况是:想加一些额外的信息到异常堆栈中。

for lineno, line in enumerate(file):
    try:
        process_line(line)
    except Exception, exc:
        raise Exception("Error in line %s: %s" % (lineno, exc))

这里保留了异常信息,但却丢失了异常堆栈。有一个方法可以保留异常堆栈。下面的方法不仅可以保留异常,也可以改变异常的信息。

except Exception, exc:
    args = exc.args
    if not args:
        arg0 = ''
    else:
        arg0 = args[0]
    arg0 += ' at line %s' % lineno
    exc.args = arg0 + args[1:]
    raise

有些小尴尬。从技术上讲(虽然它不建议使用),你可以抛出任何的异常。如果使用except Exception:那么就不能捕获一些例如string异常或者其他异常。想要这么用取决于是否关心这些场景。不过异常也可能没有.args属性,或者异常的字符串信息不能从这些参数中获取,又或者有其他的方法展示(例如KeyError信息有些不同 ) 。因此这不是万能的。想要增强版,可以这么做:

except:
    exc_class, exc, tb = sys.exc_info()

exc_class是一个字符串类型,不过可能有人会raise "not found"。所以这种风格被废弃了。如果坚持想去把周围的东西搞的一团糟,可以这么用:

new_exc = Exception("Error in line %s: %s"
                    % (lineno, exc or exc_class))
raise new_exc.__class__, new_exc, tb

这样改变了异常类周围使得它变的混乱了,但至少保留完好异常堆栈。在异常堆栈中raise ValueError(...)或者在错误信息raise Exception可能看上去有些奇怪。

小结:Python2中的较佳方法

以下是在Python2中一个较佳重新抛出异常的方法。

try:
    code()
except:
    exc_info = sys.exc_info()
    try:
        revert_stuff()
    except:
        # If this happens, it clobbers exc_info, which is why we had
        # to save it above
        import traceback
        print >> sys.stderr, "Error in revert_stuff():"
        traceback.print_exc()
        raise exc_info[0], exc_info[1], exc_info[2]

在with语句中抛出异常

如果想在某些场景下保留异常,有些场景下又想不捕获异常,那么可以怎么做

class ignore_or_reraise_exception(object):
    def __init__(self, reraise=True):
        self.reraise = reraise

    def __enter__(self):
        self.type_, self.value, self.tb, = sys.exc_info()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            if self.reraise:
                log.error('Original exception being dropped: %s' % self.value)
        if self.reraise:
            return True   #  reraise exception
        return False    # ignore exception

....
except Exception:
   with ignore_or_reraise_exception(reraise=False) as ctxt:
       if statements to determine whether to raise a new exception:
           # Not raising a new exception, so reraise
           ctxt.reraise = True

通过这个方法,可以自由的选择是忽略异常,还是想继续抛出异常。当需要抛出异常的时候,指定属性ctxt.reraise = True即可。

另外可以在ignore_or_reraise_exception中的处理异常,可以把异常都收集起来到一个全局变量,最后程序退出的时候,把所有的异常都打印出来。

总结:兼容Python2、Python3的重新抛出异常方法

在Python2中,重新抛出异常是用raise exc_info[0], exc_info[1], exc_info[2]这样的三个参数。但在Python3中却不一样了。

这个时候只能使用兼容Python2、Python3的类库six了。

使用方法

def some_code():
    raise Exception('oops, some error occur')

class SwaggerValidationError(Exception):
    pass

try:
    some_code()
except Exception as e:
    import six, sys
    six.reraise(
                SwaggerValidationError,
                SwaggerValidationError(str(e)),
                sys.exc_info()[2])

Traceback (most recent call last):
  File "5.py", line 14, in <module>
    sys.exc_info()[2])
  File "5.py", line 8, in <module>
    some_code()
  File "5.py", line 2, in some_code
    raise Exception('oops, some error occur')
__main__.SwaggerValidationError: oops, some error occur

参考文章:http://www.ianbicking.org/blog/2007/09/re-raising-exceptions.html

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: Python重新抛出异常的姿势 – https://www.chenyudong.com/archives/python-reraise-exception.html

分类: Python

Graphviz安装失败的一个尝试 » « sphinx doc正式支持中文搜索啦

1 评论

  1. if self.reraise: return True # reraise exception return False # ignore exception

    写反了吧 If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value.

发表评论

邮箱(不会被公开)

*

Copyright © 2017 东东东 陈煜东的博客 粤ICP备13059639号-1

SITEMAP回到顶部 ↑