异常处理在任何一门编程语言里都是值得关注的一个话题,良好的异常处理可以让你的程序更加健壮,清晰的错误信息更能帮助你快速修复问题。在 Python 中,和不部分高级语言一样,使用了 try/except/finally 语句块来处理异常,如果你有其他编程语言的经验,实践起来并不难。
- def div(a, b) : try: print(a / b) except ZeroDivisionError: print("Error: b should not be 0 !!") except Exception as e: print("Unexpected Error: {}".format(e))
- else: print('Run into else only when everything goes well')
- finally: print('Always run into finally block.')#tests div(2, 0) div(2, 'bad type') div(1, 2)#Mutiple exception in one line
- try: print(a / b) except(ZeroDivisionError, TypeError) as e: print(e)#Except block is optional when there is
- finally
- try: open(database)
- finally: close(database)#
- catch all errors and log it
- try: do_work() except: #get detail from logging module logging.exception('Exception caught!')
- #get detail from sys.exc_info() method error_type,
- error_value,
- trace_back = sys.exc_info() print(error_value) raise
语句不是必须的,
- except
语句也不是必须的,但是二者必须要有一个,否则就没有
- finally
的意义了。
- try
语句可以有多个,Python 会按
- except
语句的顺序依次匹配你指定的异常,如果异常已经处理就不会再进入后面的
- except
语句。
- except
语句可以以元组形式同时指定多个异常,参见实例代码。
- except
语句后面如果不指定异常类型,则默认捕获所有异常,你可以通过 logging 或者 sys 模块获取当前异常。
- except
,后面不要带任何参数或信息。
- raise
语句,
- with
方法。
- getattr()
如果你需要自主抛出异常一个异常,可以使用
关键字,等同于 C# 和 Java 中的
- raise
语句,其语法规则如下。
- throw
- raise NameError("bad name!")
关键字后面需要指定你抛出的异常类型,一般来说抛出的异常越详细越好,Python 在
- raise
模块内建了很多的异常类型,通过使用
- exceptions
函数来查看
- dir()
中的异常类型,如下:
- exceptions
- import exceptions# ['ArithmeticError', 'AssertionError'.....] print dir(exceptions)
当然你也可以查阅 Python 的文档库进行更详细的了解。
Python 中也可以自定义自己的特殊类型的异常,只需要要从
类继承 (直接或间接) 即可:
- Exception
- class SomeCustomException(Exception) : pass
一般你在自定义异常类型时,需要考虑的问题应该是这个异常所应用的场景。如果内置异常已经包括了你需要的异常,建议考虑使用内置 的异常类型。比如你希望在函数参数错误时抛出一个异常,你可能并不需要定义一个
,使用内置的
- InvalidArgumentError
即可。
- ValueError
捕捉到了异常,但是又想重新引发它(传递异常),使用不带参数的
语句即可:
- raise
- def f1() : print(1 / 0) def f2() : try: f1() except Exception as e: raise#don 't raise e !!!
- f2()'
在 Python2 中,为了保持异常的完整信息,那么你捕获后再次抛出时千万不能在
后面加上异常对象,否则你的
- raise
信息就会从此处截断 。以上是最简单的重新抛出异常的做法。
- trace
还有一些技巧可以考虑,比如抛出异常前对异常的信息进行更新。
- def f2() : try: f1() except Exception as e: e.args += ('more info', ) raise
如果你有兴趣了解更多,建议阅读这篇博客。
Python3 对重复传递异常有所改进,你可以自己尝试一下,不过建议还是同上。
当我们要捕获一个通用异常时,应该用
还是
- Exception
?我建议你还是看一下 ,这两个异常到底有啥区别呢? 请看它们之间的继承关系。
- BaseException
- BaseException + --SystemExit + --KeyboardInterrupt + --GeneratorExit + --Exception + --StopIteration... + --StandardError... + --Warning...
从
的层级结构来看,
- Exception
是最基础的异常类,
- BaseException
继承了它。
- Exception
除了包含所有的
- BaseException
外还包含了
- Exception
,
- SystemExit
和
- KeyboardInterrupt
三个异常。
- GeneratorExit
有此看来你的程序在捕获所有异常时更应该使用
而不是
- Exception
,因为另外三个异常属于更高级别的异常,合理的做法应该是交给 Python 的解释器处理。
- BaseException
代码示例如下:
- try: do_something() except NameError as e: #should pass except KeyError,
- e: #should not pass
在 Python2 的时代,你可以使用以上两种写法中的任意一种。在 Python3 中你只能使用第一种写法,第二种写法被废弃掉了。第一个种写法可读性更好,而且为了程序的兼容性和后期移植的成本,请你也抛弃第二种写法。
把字符串当成异常抛出看上去是一个非常简洁的办法,但其实是一个非常不好的习惯。
- if is_work_done() : pass
- else: raise "Work is not done!"#not cool
上面的语句如果抛出异常,那么会是这样的:
- Traceback(most recent call last) : File "/demo/exception_hanlding.py",
- line 48,
- in<module > raise "Work is not done!"TypeError: exceptions must be old - style classes or derived from BaseException,
- not str
这在 Python2.4 以前是可以接受的做法,但是没有指定异常类型有可能会让下游没办法正确捕获并处理这个异常,从而导致你的程序挂掉。简单说,这种写法是是封建时代的陋习,应该扔了。
Python 本身提供了很多的语法范式简化了异常的处理,比如
语句就处理的
- for
异常,让你很流畅地写出一个循环。
- StopIteration
语句在打开文件后会自动调用
- with
中的关闭文件操作。我们在写 Python 代码时应该尽量避免在遇到这种情况时还使用 try/except/finally 的思维来处理。
- finally
- #should not
- try: f = open(a_file) do_something(f)
- finally: f.close()#should with open(a_file) as f: do_something(f)
再比如,当我们需要访问一个不确定的属性时,有可能你会写出这样的代码:
- try: test = Test() name = test.name#not sure
- if we can get its name except AttributeError: name = 'default'
其实你可以使用更简单的
来达到你的目的。
- getattr()
- name = getattr(test, 'name', 'default')
最佳实践不限于编程语言,只是一些规则和填坑后的收获。
来源: http://www.tuicool.com/articles/ryqqmu2