In the process of running the program, if an error occurs, you can agree to return an error code beforehand, so that you can know if there is a mistake, and the cause of the error. It is common to return error codes in calls provided by the operating system. For example, a function that opens a file open()
, returns a file descriptor (that is, an integer) when it succeeds, and returns when an error occurs -1
.
It is inconvenient to use an error code to indicate whether an error occurred, because the function itself should return the normal result and the error code mixed together, causing the caller to use a lot of code to determine whether the error:
def foo(): r = some_function() if r==(-1): return (-1) # do something return rdef bar(): r = foo() if r==(-1): print ‘Error‘ else: pass
Once an error occurs, a first-level escalation is needed until a function can handle the error (for example, to output an error message to the user).
So high-level languages usually have a set try...except...finally...
of error-handling mechanisms built in, and Python is no exception.
Try
Let's take a look at try
the mechanism:
try: print ‘try...‘ r = 10 / 0 print ‘result:‘, rexcept ZeroDivisionError, e: print ‘except:‘, efinally: print ‘finally...‘print ‘END‘
When we think that some code may be wrong, it can be used try
to run the code, if the execution error, the subsequent code will not continue to execute, but directly to the error-handling code, that is, the except
statement block, except
after execution, if there is a finally
statement block, then execute the finally
statement block, This completes the execution.
The above code 10 / 0
generates a division operation error when it evaluates:
try...except: integer division or modulo by zerofinally...END
As can be seen from the output, when an error occurs, subsequent statements are print ‘result:‘, r
not executed, except
because they ZeroDivisionError
are captured and therefore executed. Finally, the finally
statement is executed. The program then continues to follow the process down.
If the divisor 0
is changed 2
, the result of execution is as follows:
try...result: 5finally...END
Because there is no error, the except
statement block will not be executed, but finally
if there is one, it will be executed (without a finally
statement).
You can also guess that there are many kinds of errors, and if different types of errors occur, they should be except
handled by different blocks of statements. Yes, there can be multiple except
to catch different types of errors:
try: print ‘try...‘ r = 10 / int(‘a‘) print ‘result:‘, rexcept ValueError, e: print ‘ValueError:‘, eexcept ZeroDivisionError, e: print ‘ZeroDivisionError:‘, efinally: print ‘finally...‘print ‘END‘
int()
The function may be thrown ValueError
, so we use one except
capture ValueError
, with another except
capture ZeroDivisionError
.
In addition, if no error occurs, you can except
add one after the statement block else
and automatically execute the statement when no error occurs else
:
try: print ‘try...‘ r = 10 / int(‘a‘) print ‘result:‘, rexcept ValueError, e: print ‘ValueError:‘, eexcept ZeroDivisionError, e: print ‘ZeroDivisionError:‘, eelse: print ‘no error!‘finally: print ‘finally...‘print ‘END‘
Python's error is also class, all the error types are inherited from BaseException
, so in the use except
of note that it not only captures the type of error, but also its subclasses "clean sweep". Like what:
try: foo()except StandardError, e: print ‘StandardError‘except ValueError, e: print ‘ValueError‘
The second one except
is never caught ValueError
, because ValueError
StandardError
the subclass, if any, is also captured by the first one except
.
All of Python's errors are BaseException
derived from the class, common error types and inheritance relationships look here:
Https://docs.python.org/2/library/exceptions.html#exception-hierarchy
try...except
There is a huge benefit of using a catch error, which is that you can span multiple layers of calls, such as function main()
calls, foo()
foo()
calls, and bar()
result bar()
errors, which can be handled as soon as they are main()
captured:
def foo(s): return 10 / int(s)def bar(s): return foo(s) * 2def main(): try: bar(‘0‘) except StandardError, e: print ‘Error!‘ finally: print ‘finally...‘
In other words, there is no need to catch errors in every possible error, as long as the error is captured at the appropriate level. In this way, it greatly reduces the try...except...finally
trouble of writing.
Call stack
If the error is not captured, it is thrown up and then caught by the Python interpreter, prints an error message, and the program exits. Look at err.py
:
# err.py:def foo(s): return 10 / int(s)def bar(s): return foo(s) * 2def main(): bar(‘0‘)main()
Execution, the results are as follows:
$ python err.pyTraceback (most recent call last): File "err.py", line 11, in <module> main() File "err.py", line 9, in main bar(‘0‘) File "err.py", line 6, in bar return foo(s) * 2 File "err.py", line 3, in foo return 10 / int(s)ZeroDivisionError: integer division or modulo by zero
It's not scary to make a mistake, but it's scary to know where it went wrong. Interpreting error messages is the key to locating errors. We can see the entire wrong call function chain from the top down:
Error message Line 1th:
Traceback (most recent call last):
Tell us that this is the wrong tracking information.
Line 2nd:
File "err.py", line 11, in <module> main()
The call main()
made an error in the err.py
11th line of code in the code file, but the reason is line 9th:
File "err.py", line 9, in main bar(‘0‘)
The call bar(‘0‘)
made an error in the err.py
9th line of code in the code file, but the reason is line 6th:
File "err.py", line 6, in bar return foo(s) * 2
The reason is that return foo(s) * 2
this statement is wrong, but this is not the final reason, continue to look down:
File "err.py", line 3, in foo return 10 / int(s)
The reason is that return 10 / int(s)
this statement went wrong, which is the source of the error because it prints:
ZeroDivisionError: integer division or modulo by zero
Depending on the type of error, ZeroDivisionError
we judge that int(s)
there is no error in itself, but int(s)
returns 0
, when the calculation 10 / 0
is wrong, to find the source of the error.
Log errors
If you do not catch an error, it is natural for the Python interpreter to print out the error stack, but the program is ended. Now that we can catch the error, we can print out the error stack, analyze the cause of the error, and let the program go on.
Python's built-in logging
modules make it very easy to log error messages:
# err.pyimport loggingdef foo(s): return 10 / int(s)def bar(s): return foo(s) * 2def main(): try: bar(‘0‘) except StandardError, e: logging.exception(e)main()print ‘END‘
The same error occurs, but the program continues to execute after printing the error message and exits normally:
$ python err.pyERROR:root:integer division or modulo by zeroTraceback (most recent call last): File "err.py", line 12, in main bar(‘0‘) File "err.py", line 8, in bar return foo(s) * 2 File "err.py", line 5, in foo return 10 / int(s)ZeroDivisionError: integer division or modulo by zeroEND
Through the configuration, the logging
error can also be recorded in the log file, to facilitate the subsequent troubleshooting.
Throw error
Because the error is class, capturing an error is capturing an instance of the class. As a result, errors are not generated in a vacuum, but are intentionally created and thrown. Python's built-in functions throw many types of errors, and the functions we write ourselves can also throw errors.
If you want to throw an error, you can first define an incorrect class, choose a good inheritance relationship, and then raise
throw an instance of the error with the statement, as needed:
# err.pyclass FooError(StandardError): passdef foo(s): n = int(s) if n==0: raise FooError(‘invalid value: %s‘ % s) return 10 / n
execution, you can finally trace to our own definition of the error:
$ python err.pyTraceback (most recent call last): ...__main__.FooError: invalid value: 0
Define our own error types only when necessary. If you can choose a built-in error type that Python already has (such as valueerror,typeerror), use the Python built-in error type as much as possible.
Finally, let's look at another way of handling errors:
# err.pydef foo(s): n = int(s) return 10 / ndef bar(s): try: return foo(s) * 2 except StandardError, e: print ‘Error!‘ raisedef main(): bar(‘0‘)main()
In the bar()
function, we have obviously caught the error, but, after printing one Error!
, and then throw the error through the raise
statement, this is not sick?
In fact, this method of error treatment is not only not sick, but also quite common. The purpose of capturing errors is simply to record and facilitate follow-up. However, because the current function does not know what to do with the error, the most appropriate way is to continue to throw up and let the top-level caller handle it.
raise
If the statement is not with a parameter, the current error is thrown. In addition, except
in raise
one error, you can convert one type of error to another type:
try: 10 / 0except ZeroDivisionError: raise ValueError(‘input error!‘)
As long as it is reasonable to convert the logic can be, but, should never be IOError
converted into irrelevant ValueError
.
Summary
Python built-in try...except...finally
to handle errors is very convenient. When an error occurs, it is critical to parse the error message and locate the code location where the error occurred.
The program can also actively throw an error, allowing the caller to handle the corresponding error. However, you should write clearly in your document what errors might be thrown and why the error occurred.
Python error handling