重复引起异常 Re-raising Exceptions
有的时候清除工作需要对错误处理和正确处理是不同的。例如,数据库操作错误需要回滚事务,但是没有错误需要commit操作。这种情况下,你必须要捕获异常并且处理它。中间的层异常 需要被捕获取消之前执行的部分操作,然后继续传播给上层的错误处理。
#!/usr/bin/env python """Illustrate database transaction management using sqlite3. """ import logging import os import sqlite3 import sys DB_NAME = ‘mydb.sqlite‘ logging.basicConfig(level=logging.INFO) log = logging.getLogger(‘db_example‘) def throws(): raise RuntimeError(‘this is the error message‘) def create_tables(cursor): log.info(‘Creating tables‘) cursor.execute("create table module (name text, description text)") def insert_data(cursor): for module, description in [(‘logging‘, ‘error reporting and auditing‘), (‘os‘, ‘Operating system services‘), (‘sqlite3‘, ‘SQLite database access‘), (‘sys‘, ‘Runtime services‘), ]: log.info(‘Inserting %s (%s)‘, module, description) cursor.execute("insert into module values (?, ?)", (module, description)) return def do_database_work(do_create): db = sqlite3.connect(DB_NAME) try: cursor = db.cursor() if do_create: create_tables(cursor) insert_data(cursor) throws() except: db.rollback() log.error(‘Rolling back transaction‘) raise else: log.info(‘Committing transaction‘) db.commit() return def main(): do_create = not os.path.exists(DB_NAME) try: do_database_work(do_create) except Exception, err: log.exception(‘Error while doing database work‘) return 1 else: return 0 if __name__ == ‘__main__‘: sys.exit(main())
这个案例中在do_database_work()中使用了一个分离的异常处理,取消之前的数据库操作,然后全局的异常处理器会打印出错误信息。
$ python sqlite_error.py INFO:db_example:Creating tables INFO:db_example:Inserting logging (error reporting and auditing) INFO:db_example:Inserting os (Operating system services) INFO:db_example:Inserting sqlite3 (SQLite database access) INFO:db_example:Inserting sys (Runtime services) ERROR:db_example:Rolling back transaction ERROR:db_example:Error while doing database work Traceback (most recent call last): File "sqlite_error.py", line 51, in main do_database_work(do_create) File "sqlite_error.py", line 38, in do_database_work throws() File "sqlite_error.py", line 15, in throws raise RuntimeError(‘this is the error message‘) RuntimeError: this is the error message
保留错误跟踪信息 Preserving Tracebacks
很多时候在你的程序中,异常中清理程序自己又引起了其他的异常。这种情况一般是发生在系统资源(内存,硬盘资源等..)不足的时候。在异常处理中引起的异常可能会覆盖了原先根本的异常,如果没有对这些异常中的异常没有被处理。
#!/usr/bin/env python import sys import traceback def throws(): raise RuntimeError(‘error from throws‘) def nested(): try: throws() except: cleanup() raise def cleanup(): raise RuntimeError(‘error from cleanup‘) def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1 if __name__ == ‘__main__‘: sys.exit(main())
当在处理原本错误的时候,cleanup()方法引起一个异常,那么异常处理机制就会重置去处理新的错误。(所以我们只看到异常中的异常了,原本引起的异常就没有了)
$ python masking_exceptions.py Traceback (most recent call last): File "masking_exceptions.py", line 21, in main nested() File "masking_exceptions.py", line 13, in nested cleanup() File "masking_exceptions.py", line 17, in cleanup raise RuntimeError(‘error from cleanup‘) RuntimeError: error from cleanup
即使第二个异常被捕获了,也没法保证原本的异常被保存。
#!/usr/bin/env python import sys import traceback def throws(): raise RuntimeError(‘error from throws‘) def nested(): try: throws() except: try: cleanup() except: pass # ignore errors in cleanup raise # we want to re-raise the original error def cleanup(): raise RuntimeError(‘error from cleanup‘) def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1 if __name__ == ‘__main__‘: sys.exit(main())在这里,即使我们把cleanup()的调用封装在一个忽略异常的异常处理块里面,cleanup()引起的错误也会覆盖原本的错误,因为上下文中只有一个异常被保存。
$ python masking_exceptions_catch.py Traceback (most recent call last): File "masking_exceptions_catch.py", line 24, in main nested() File "masking_exceptions_catch.py", line 14, in nested cleanup() File "masking_exceptions_catch.py", line 20, in cleanup raise RuntimeError(‘error from cleanup‘) RuntimeError: error from cleanup一种非常幼稚的做法是捕获原本的异常,保存在一个变量中,然后明确的再次引起这个异常。
#!/usr/bin/env python import sys import traceback def throws(): raise RuntimeError(‘error from throws‘) def nested(): try: throws() except Exception, original_error: try: cleanup() except: pass # ignore errors in cleanup raise original_error def cleanup(): raise RuntimeError(‘error from cleanup‘) def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1 if __name__ == ‘__main__‘: sys.exit(main())
正如你看到的,这种方式不能保存所有的错误跟踪。栈追踪根本就没有打印throws()方法。虽然打印的是原始的错误。
$ python masking_exceptions_reraise.py Traceback (most recent call last): File "masking_exceptions_reraise.py", line 24, in main nested() File "masking_exceptions_reraise.py", line 17, in nested raise original_error RuntimeError: error from throws ’‘’ 更好的做法是先重新引起一个原始的异常,然后在try:finally块中进行清除。 ‘’‘ #!/usr/bin/env python import sys import traceback def throws(): raise RuntimeError(‘error from throws‘) def nested(): try: throws() except Exception, original_error: try: raise finally: try: cleanup() except: pass # ignore errors in cleanup def cleanup(): raise RuntimeError(‘error from cleanup‘) def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1 if __name__ == ‘__main__‘: sys.exit(main())
这种结构防止了原始的异常被后来的异常覆盖的情况,并且把所有的错误信息保存到错误跟踪栈中。
$ python masking_exceptions_finally.py Traceback (most recent call last): File "masking_exceptions_finally.py", line 26, in main nested() File "masking_exceptions_finally.py", line 11, in nested throws() File "masking_exceptions_finally.py", line 7, in throws raise RuntimeError(‘error from throws‘) RuntimeError: error from throws这种特别的缩进可能不是很好看,但是输出了所有想要的信息。原始的错误信息被打印出来了,其中也包括了所有的错误跟踪。