异常捕获

try...except...finally

Python支持异常处理,最简单的捕获异常的方式就是try...except...finally字句,

try:
    1 / 0
except:
    print 'except'
finally:
    print 'finally'

except字句中不指定类型时意味着捕获所有类型异常,也可以在其中指定具体异常类型,

try:
    1 / 0
except ZeroDivisionError as e:
    print 'except', e

多个异常可以在不同except子句中进行捕获,也可以在一个except字句中处理多种类型异常,

try:
    1 / 0
except ZeroDivisionError as e:
    print 'except', e
except IOError as e:
    print 'except', e
try:
    1 / 0
except (ZeroDivisionError, IOError) as e:
    print 'except', e

try...except...else

Python还支持try...except...else语句,当没有指定异常抛出的时候,else中的内容就会被执行,

try:
    1 / 1
except ZeroDivisionError as e:
    print 'except', e
else:
    print 'no exception'

with

Python中的with子句可以简化频繁出现的try...except...finally处理,最常见的如打开文件的相关处理,

with open('input.txt') as inputs:
    pass

__enter__, __exit__

想使用with字句,可以通过实现context manager type协议来支持with字句,

contextmanager.__enter__()
contextmanager.__exit__(exc_type, exc_val, exc_tb)

一个简单的例子,

class Foobar(object):

    def __enter__(self):
        print '__enter__'

    def __exit__(self, exc_type, exc_val, exc_tb):
        print '__exit__', exc_type, exc_val, exc_tb
        return True

with Foobar() as foobar:
    1 / 0

如上__enter__函数在进入with子句时会被调用,__exit__在退出时调用,如果存在异常,则异常信息通过参数传入。__exit__ 需要返回bool值用以表明异常是否在函数内已经被处理。

contextlib

Python还提供了contextlib以简化with子句的定义,使用contextlib.contextmanager,

from contextlib import contextmanager

@contextmanager
def foobar(msg):
    print 'start'
    try:
	    yield msg
	 except:
	    print 'except'
	 finally:
	    print 'finally'
    print 'end'

with foobar("foobar") as name:
    print 'contextmanager'

被contextmanager修饰的函数需要返回generator。

异常内容展示

sys & traceback

当异常对象被捕获后,一个自然的需求就是输出异常的调用栈,但是Python并未提供类似Java中的printStackTrace函数。这个功能需要我们自己去实现。

sys.exc_info可以返回最新的异常信息,traceback模块提供了格式化函数,

try:
    1 / 0
except:
    print '\n'.join(traceback.format_exception(*sys.exc_info()))

如果只是格式化最新的异常,也可以直接调用traceback.format_exc方法,

try:
    1 / 0
except:
    print traceback.format_exc()

traceback上还提供获取当前调用栈的功能,在无需显示抛出异常的情况下,也可以清除的展示函数调用关系,

print '\n'.join(traceback.format_stack())

inspect

通过sys & traceback上提供的方法,我们能获取到异常调用栈,不过很多时候我们会希望能够获取当更多的信息,比如函数调用过程中传递的参数、局部变量值等等,这些对解决异常排查问题很有帮助。通过inspect上的方法,可以获取当上述这些内容。

def get_detail_stack_info():
    stack_list = inspect.stack()[1:]
    stack_list.reverse()
    result = []
    for idx, item in enumerate(stack_list):
        frame, path, line, name, code, num = item
        info = {
            "path": path,
            "line": line,
            "name": name,
            "code": '.'.join(['\t' + x.strip() for x in code]),
            "args": inspect.formatargvalues(*inspect.getargvalues(frame))
        }
        result.append('File: "{path}", line {line}, in {name}\n{code}, args: {args}\n'.format(**info))
    return result

inspect.stack可以获取到调用栈,在这之后在通过相关接口从frame上获取到函数调用的参数信息。

sys.excepthook

Java Thread类提供了setUncaughtExceptionHandler方法用以处理没有没捕获的异常。

public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

Python也提供了相近的功能,这个功能存在于sys.excepthook。excepthook定义如下,

sys.excepthook(type, value, traceback)

我们可以自定义具备相同函数签名的函数,替换掉默认的excepthook,

def default_exception_hook(exp_type, exp_value, exp_traceback):
    exp_info = '\n'.join(traceback.format_exception(exp_type, exp_value, exp_traceback))
    print exp_info

sys.excepthook = hook_func

1 / 0

总结

Python的异常处理与Java之类有不少相似之处,不过就接口封装而言,Python的有些过于分散。上面只是介绍了如果捕获到异常,获取到异常消息之后根据具体的需求可以进一步再处理,比如将异常消息写入日志、通过异常收集系统收集异常信息等等,有机会再来记录后续这些方法吧。