[Python] Exception Handling Technology (2)
Re-raising Exceptions
In some cases, clearing jobs requires different error handling and correct handling. For example, if a database operation error occurs, you need to roll back the transaction, but there is no error, You need to perform the commit operation. In this case, you must capture an exception and process it. An intermediate layer exception needs to be captured and removed before some operations are executed, and then the error handling process is further propagated to the upper layer.
#!/usr/bin/env python"""Illustrate database transaction management using sqlite3."""import loggingimport osimport sqlite3import sysDB_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)) returndef 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() returndef 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 0if __name__ == '__main__': sys.exit(main())
In this case, a separate exception handling is used in do_database_work () to cancel the previous database operation, and the global exception processor prints an error message.
$ python sqlite_error.pyINFO:db_example:Creating tablesINFO: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 transactionERROR:db_example:Error while doing database workTraceback (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
Keep Error Tracking Information Preserving Tracebacks
Many times in your program, cleaning the program itself causes other exceptions. This usually happens when the system resources (memory, hard disk resources, etc.) are insufficient. Exceptions caused by exception handling may overwrite the original fundamental exceptions, if they are not handled.
#!/usr/bin/env pythonimport sysimport tracebackdef throws(): raise RuntimeError('error from throws')def nested(): try: throws() except: cleanup() raisedef cleanup(): raise RuntimeError('error from cleanup')def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1if __name__ == '__main__': sys.exit(main())
When the cleanup () method causes an exception when processing the original error, the exception handling mechanism will reset to handle the new error. (So we can only see the exception in the exception, and the exception is lost)
$ python masking_exceptions.pyTraceback (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
Even if the second exception is caught, the original exception cannot be saved.
#!/usr/bin/env pythonimport sysimport tracebackdef 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 errordef cleanup(): raise RuntimeError('error from cleanup')def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1if __name__ == '__main__': sys.exit(main())
Here, even if we encapsulate cleanup () calls in an exception handling block that ignores exceptions, the errors caused by cleanup () will overwrite the original errors, because only one exception is saved in the context.
$ python masking_exceptions_catch.pyTraceback (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
One very naive way is to catch the original exception, save it in a variable, and then explicitly cause this exception again.
#!/usr/bin/env pythonimport sysimport tracebackdef throws(): raise RuntimeError('error from throws')def nested(): try: throws() except Exception, original_error: try: cleanup() except: pass # ignore errors in cleanup raise original_errordef cleanup(): raise RuntimeError('error from cleanup')def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1if __name__ == '__main__': sys.exit(main())
As you can see, this method cannot save all error traces. Stack tracing does not print the throws () method at all. Although the print is the original error.
$ Python Merge (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_errorRuntimeError: error from throws ''' a better way is to first re-cause the original exception and then clear it in try: finally block. '''#! /Usr/bin/env pythonimport sysimport tracebackdef throws (): raise RuntimeError ('error from throws ') def nested (): try: throws () handle T Exception, original_error: try: raise finally: try: cleanup () failed T: pass # ignore errors in cleanupdef cleanup (): raise RuntimeError ('error from cleanup') def main (): try: nested () return 0 failed t Exception, err: traceback. print_exc () return 1if _ name _ = '_ main _': sys. exit (main ())
This structure prevents original exceptions from being overwritten by subsequent exceptions and saves all error information to the Error Tracking stack.
$ python masking_exceptions_finally.pyTraceback (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
This special indent may not look good, But It outputs all the information you want. The original error information is printed out, including all error traces.