"Python core technology and practice" exception handling: how to improve program stability?

Exception handling: how to improve program stability?

1, Errors and exceptions

  • First, what are the errors and exceptions in Python? What are the connections and differences between the two?
  • Generally speaking, there are at least two kinds of errors in the program, one is syntax error, the other is exception.
  • You should be clear about the so-called syntax error, that is, the code you write does not conform to the programming specification and cannot be recognized and executed. For example, the following example:
if name is not None
    print(name)
  • If statements omit colons and do not conform to Python syntax, so the program will report an error invalid syntax.
  • The exception means that the syntax of the program is correct and can be executed, but an error is encountered during execution and an exception is thrown, such as the following three examples:
10 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

order * 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'order' is not defined

1 + [1, 2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'list'
  • Their syntax is completely correct, but obviously, we can't make the denominator 0 when we divide; You cannot use undefined variables for operations; It is also undesirable to add an integer to a list.
  • So, when the program runs to these places, it throws an exception and terminates. Zerodivisionerror, NameError and TypeError in the example are three common exception types.
  • Of course, there are many other exception types in Python. For example, KeyError means that the key in the dictionary cannot be found; FileNotFoundError refers to the request sent to read the file, but the corresponding file does not exist. I will not repeat it here. You can refer to it by yourself: Corresponding documents

2, How to handle exceptions

  • Just mentioned, if an exception is thrown somewhere in the program, the program will be terminated and exit. You may ask, is there any way to keep the program running without terminating it? Of course, the answer is yes. This is what we call exception handling. We usually use try and except to solve it, such as:
try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ... 
except ValueError as err:
    print('Value Error: {}'.format(err))

print('continue')
...
  • Here, by default, the user inputs two integer numbers separated by commas, extracts them, and then performs subsequent operations (note that the input function will convert the input to string type). If we enter a,b, the program will throw an exception invalid literal for int() with base 10: 'a', and then jump out of the try block.
  • Because the exception type thrown by the program is ValueError, which matches the exception type caught by the exception block, the exception block will be executed, and finally output Value Error: invalid literal for int() with base 10: 'a', and print continue.
please enter two numbers separated by comma: a,b
Value Error: invalid literal for int() with base 10: 'a'
continue
  • Exception block only accepts and executes the exception types that match it. If the exceptions thrown by the program do not match, the program will still terminate and exit.
  • Therefore, in this example, if we only enter 1, the exception thrown by the program is IndexError: list index out of range, which does not match ValueError, then the exception block will not be executed, and the program will terminate and exit (continue will not be printed).
please enter two numbers separated by comma: 1
IndexError Traceback (most recent call last)
IndexError: list index out of range
  • However, it is clear that this emphasis on one type of writing has great limitations. So, how to solve this problem?
  • One solution is to add a variety of exception types to the exception block, such as the following:
try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ...
except (ValueError, IndexError) as err:
    print('Error: {}'.format(err))
    
print('continue')
...
  • Or the second way:
try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ...
except ValueError as err:
    print('Value Error: {}'.format(err))
except IndexError as err:
    print('Index Error: {}'.format(err))

print('continue')
...
  • In this way, every time the program executes, only one exception type in the exception block matches the actual.
  • However, in many cases, it is difficult to ensure that the program covers all exception types. Therefore, a more common approach is to declare that the exception type handled by the last exception block is exception. Exception is the base class of all other non system exceptions and can match any non system exception. Then this code can be written as follows:
try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ...
except ValueError as err:
    print('Value Error: {}'.format(err))
except IndexError as err:
    print('Index Error: {}'.format(err))
except Exception as err:
    print('Other error: {}'.format(err))

print('continue')
...
  • Alternatively, you can omit the exception type after exception, which means that it matches any exception (including system exception):
try:
    s = input('please enter two numbers separated by comma: ')
    num1 = int(s.split(',')[0].strip())
    num2 = int(s.split(',')[1].strip())
    ...
except ValueError as err:
    print('Value Error: {}'.format(err))
except IndexError as err:
    print('Index Error: {}'.format(err))
except:
    print('Other error')

print('continue')
...
  • It should be noted that when there are multiple except blocks in the program, at most one except block will be executed. In other words, if the exception types declared by multiple exceptions match the actual, only the first exception block will be executed, and the others will be ignored.
  • < font color = B in exception handling, another common usage is finally, which is often used together with try and except. No matter what happens, the statements in the finally block will be executed, even if the return statement is used in the previous try and except block.
  • A common application scenario is file reading:
import sys
try:
    f = open('file.txt', 'r')
    .... # some data processing
except OSError as err:
    print('OS error: {}'.format(err))
except:
    print('Unexpected error:', sys.exc_info()[0])
finally:
    f.close()
  • In this code, the try block attempts to read the file.txt and perform a series of processing on the data in it. In the end, whether the reading is successful or failed, the program will execute the statement in finally - close the file stream to ensure the integrity of the file. Therefore, in finally, we usually put some statements to be executed anyway.
  • It is worth mentioning that we often use with open for reading files. You may have seen in the previous example that with open will automatically close the file at the end, making the statement more concise.

3, User defined exception

  • The previous examples are full of Python built-in exception types. You may ask, can I create my own exception types?
  • The answer is yes, Python certainly allows us to do so. In the following example, we create a custom exception type MyInputError, define and implement the initialization function and str function (called directly when print ing):
class MyInputError(Exception):
    """Exception raised when there're errors in input"""
    def __init__(self, value): # Initialization of custom exception types
        self.value = value
    def __str__(self): # Custom string representation of exception types
        return ("{} is invalid input".format(repr(self.value)))
    
try:
    raise MyInputError(1) ################# Throw the exception MyInputError
except MyInputError as err:
    print('error: {}'.format(err))

############ The output results are as follows:
# error: 1 is invalid input
  • In practice, if the built-in exception type cannot meet our needs, or in order to make the exception more detailed and readable, we want to add some other functions of the exception type, we can customize the required exception type. However, in most cases, Python's built-in exception types are good enough.

4, Usage scenarios and precautions of exceptions

  • Generally speaking, in a program, if we are not sure whether a piece of code can be executed successfully, we often need to use exception handling in this place. In addition to the above example of file reading, I can give another example to illustrate.
  • The background of large social networking sites needs to return corresponding records for the requests sent by users. User records are often stored in the database with key value structure. Every time a request comes, we get the user's ID and use the ID to query the record of this person in the database to return the corresponding results.
  • The original data returned by the database is often in the form of json string, which requires us to decode the json string first. You may easily think of the following methods:
import json
raw_data = queryDB(uid) # The corresponding information is returned according to the user's id
data = json.loads(raw_data)
  • Is such code enough?
  • You know, in the json.loads() function, if the input string does not meet its specification, it cannot be decoded and an exception will be thrown. Therefore, it is necessary to add exception handling.
try:
    data = json.loads(raw_data)
    ....
except JSONDecodeError as err:
    print('JSONDecodeError: {}'.format(err))
  • One thing to keep in mind, however, is that we cannot go to the other extreme - misuse of exception handling.
  • For example, when you want to find the value corresponding to a key in the dictionary, you must not write it in the following form:
d = {'name': 'jason', 'age': 20}
try:
    value = d['dob']
    ...
except KeyError as err:
    print('KeyError: {}'.format(err))
  • Admittedly, such code has no bug s, but it is confusing and redundant. If your code is full of this writing method, it is undoubtedly an obstacle to reading and collaboration. Therefore, we generally do not need exception handling for flow control code logic.
  • The dictionary example is written as follows, which is very good.
if 'dob' in d:
    value = d['dob']
    ...

5, Summary

  • In this lesson, we learned Python exception handling and its usage scenarios. You need to master the following points.
  • Exception, usually means that the program encounters an error during operation, terminates and exits. We usually use the try except ion statement to handle exceptions, so that the program will not be terminated and can continue to execute.
  • When handling exceptions, if there are statements that must be executed, such as closing the file after it is opened, they can be placed in the finally block.
  • Exception handling is usually used when you are not sure whether a piece of code can be successfully executed or can not be easily judged, such as database connection, reading, etc. For normal flow control logic, do not use exception handling, just use conditional statements to solve it directly.

Thinking questions

  • Finally, leave you a question to think about. During exception handling, if multiple exceptions are thrown in the try block, do we need to use multiple try except block s? Taking database connection and reading as an example, which of the following two ways do you think is better?
  • First:
try:
    db = DB.connect('<db path>') # Exceptions may be thrown
    raw_data = DB.queryData('<viewer_id>') # Exceptions may be thrown
except (DBConnectionError, DBQueryDataError) err:
    print('Error: {}'.format(err))
  • Second:
try:
    db = DB.connect('<db path>') # Exceptions may be thrown
    try:
        raw_data = DB.queryData('<viewer_id>')
    except DBQueryDataError as err:
         print('DB query data error: {}'.format(err))
except DBConnectionError as err:
     print('DB connection error: {}'.format(err))
  • The first method is more concise and easy to read. Moreover, the error type behind exception throws a database connection error first and then a query error. The exception handling is the same as the second.

Tags: Python

Posted on Wed, 15 Sep 2021 23:11:18 -0400 by slava