Skip to content

Python3 Errors and Exceptions

As a Python beginner, when first learning Python programming, you will often see some error messages. We haven’t mentioned them before, and this chapter will specifically introduce them.

Python has two types of errors that are easily recognizable: syntax errors and exceptions.

Python assert is used to evaluate an expression and triggers an exception when the expression condition is false.

Python syntax errors, also known as parsing errors, are frequently encountered by beginners, as shown in the following example:

>>> while True print('Hello world')
  File "<stdin>", line 1, in ?
    while True print('Hello world')
                   ^
SyntaxError: invalid syntax

In this example, the function print() is detected as having an error because it is missing a colon : before it.

The parser points out the line where the error occurred and marks a small arrow at the location of the first error found.

Even if the syntax of a Python program is correct, errors may still occur when running it. Errors detected at runtime are called exceptions.

Most exceptions are not handled by the program and are displayed here as error messages:

>>> 10 * (1/0)             # 0 cannot be used as a divisor, triggers an exception
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3             # spam is undefined, triggers an exception
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2               # int cannot be added to str, triggers an exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

Exceptions appear in different types, and these types are printed as part of the information: the types in the example are ZeroDivisionError, NameError, and TypeError.

The front part of the error message shows the context in which the exception occurred and displays specific information in the form of a call stack.

Exception catching can be done using the try/except statement.

In the following example, the user is asked to enter a valid integer, but the user is allowed to interrupt the program (using Control-C or the method provided by the operating system). The user interrupt information will trigger a KeyboardInterrupt exception.

while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("You did not enter a number, please try again!") 

The try statement works as follows:

  • First, the try clause (the statements between the try keyword and the except keyword) is executed.

  • If no exception occurs, the except clause is ignored and the try clause ends after execution.

  • If an exception occurs during the execution of the try clause, the rest of the try clause will be ignored. If the type of the exception matches the name after except, the corresponding except clause will be executed.

  • If an exception does not match any except, the exception will be passed to the upper-level try.

A try statement may contain multiple except clauses to handle different specific exceptions. At most one branch will be executed.

The handler will only handle exceptions in the corresponding try clause, not exceptions in other try handlers.

An except clause can handle multiple exceptions simultaneously. These exceptions will be placed in parentheses as a tuple, for example:

except (RuntimeError, TypeError, NameError):
    pass

The last except clause can ignore the exception name and will be used as a wildcard. You can use this method to print an error message and then throw the exception again.

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

The try/except statement also has an optional else clause. If this clause is used, it must be placed after all except clauses.

The else clause will be executed when no exception occurs in the try clause.

The following example checks whether a file can be opened in the try statement. If the file is opened normally without any exception, the else part of the statement is executed to read the file content:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()

Using the else clause is better than putting all statements in the try clause, as it can avoid some unexpected exceptions that except cannot catch.

Exception handling not only handles exceptions that occur directly in the try clause but also handles exceptions thrown by functions called in the clause (even indirectly called functions). For example:

>>> def this_fails():
        x = 1/0
   
>>> try:
        this_fails()
    except ZeroDivisionError as err:
        print('Handling run-time error:', err)
   
Handling run-time error: int division or modulo by zero

The try-finally statement will execute the final code regardless of whether an exception occurs.

In the following example, the finally statement will execute regardless of whether an exception occurs:

try:
    runoob()
except AssertionError as error:
    print(error)
else:
    try:
        with open('file.log') as file:
            read_data = file.read()
    except FileNotFoundError as fnf_error:
        print(fnf_error)
finally:
    print('This sentence will be executed regardless of whether an exception occurs.')

Python uses the raise statement to throw a specified exception.

The raise syntax format is as follows:

raise [Exception [, args [, traceback]]]

The following example triggers an exception if x is greater than 5:

x = 10
if x > 5:
    raise Exception('x cannot be greater than 5. The value of x is: {}'.format(x))

Executing the above code will trigger an exception:

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    raise Exception('x cannot be greater than 5. The value of x is: {}'.format(x))
Exception: x cannot be greater than 5. The value of x is: 10

The single parameter of raise specifies the exception to be thrown. It must be an instance of an exception or an exception class (i.e., a subclass of Exception).

If you only want to know whether an exception is thrown and don’t want to handle it, a simple raise statement can throw it again.

>>> try:
        raise NameError('HiThere')  # Simulate an exception.
    except NameError:
        print('An exception flew by!')
        raise
   
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

You can create your own exceptions by creating a new exception class. Exception classes inherit from the Exception class, either directly or indirectly, for example:

>>> class MyError(Exception):
        def __init__(self, value):
            self.value = value
        def __str__(self):
            return repr(self.value)
   
>>> try:
        raise MyError(2*2)
    except MyError as e:
        print('My exception occurred, value:', e.value)
   
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

In this example, the default __init__() of the Exception class is overridden.

When creating a module that might throw multiple different exceptions, a common practice is to create a base exception class for the package and then create different subclasses based on this base class for different error situations:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

Most exception names end with “Error,” just like standard exception naming conventions.


The try statement has another optional clause that defines cleanup actions that will be executed under any circumstances. For example:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
... 
Goodbye, world!
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt

In the above example, regardless of whether an exception occurs in the try clause, the finally clause will be executed.

If an exception is thrown in the try clause (or in the except and else clauses) and no except catches it, the exception will be thrown after the finally clause is executed.

Here is a more complex example (containing both except and finally clauses in the same try statement):

>>> def divide(x, y):
        try:
            result = x / y
        except ZeroDivisionError:
            print("division by zero!")
        else:
            print("result is", result)
        finally:
            print("executing finally clause")
   
>>> divide(2, 1)
result is 2.0
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

Some objects define standard cleanup actions. Whether the system successfully uses them or not, once they are no longer needed, the standard cleanup action will be executed.

The following example shows an attempt to open a file and then print the content to the screen:

for line in open("myfile.txt"):
    print(line, end="")

The problem with the above code is that when execution is complete, the file remains open and is not closed.

The with keyword statement can ensure that objects like files correctly execute their cleanup methods after use:

with open("myfile.txt") as f:
    for line in f:
        print(line, end="")

After the above code is executed, even if problems occur during processing, the file f will always be closed.

For more on the with keyword, see: Python with Keyword


Python assert

Python with Keyword