Python3 errors and exceptions


May 10, 2021 03:00 Python3


Table of contents


As a beginner of Python, when you first learn Python programming, you often see false information, which we didn't mention earlier, and we'll cover this section specifically.

Python has two types of errors that are easy to identify: syntax errors and exceptions.

The syntax is wrong

Python's syntax errors, or parsing errors, are often encountered by beginners, as shown in the following examples

>>> 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 checked for an error, which is that it is preceded by a colon (:).

The syntax analyzer points out the wrong line and marks a small arrow where the error was first found.

Abnormal

Even if the Syntax of the Python program is correct, an error can occur when you run it. Errors detected during the run are called exceptions.

Most exceptions are not handled by the program and are presented here in the form of error messages:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: Can't convert 'int' object to str implicitly

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

The previous section of the error message shows the context in which the exception occurred and displays specific information as a call stack.

Exception handling

In the following example, the user is asked to enter a legitimate integer, but the user is allowed to interrupt the program (using control-C or the method provided by the operating system). T he user-interrupted information throws a KeyboardInterrupt exception.

>>> while True:
        try:
            x = int(input("Please enter a number: "))
            break
        except ValueError:
            print("Oops!  That was no valid number.  Try again   ")
   

Try statements work as follows;

  • First, execute the try clause (statement between the keyword try and the keyword except)
  • If no exception occurs, ignore the except clause, which ends after execution.
  • If an exception occurs during the execution of the try clause, the rest of the try clause is ignored. I f the type of the exception matches the name after the except, the corresponding except clause is executed. The code after the try statement is finally executed.
  • If an exception does not match any of the excepts, the exception is passed to the upper try.

A try statement may contain multiple except clauses that handle different specific exceptions. At most one branch is executed.

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

An except clause can handle multiple exceptions at the same time, which are placed in parentheses and become a metagroup, for example:

>>> except (RuntimeError, TypeError, NameError):
        pass

The last except clause ignores the name of the exception, which is 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, which, if used, must be placed after all the except clauses. T his clause will be executed when no exceptions occur in the try clause. F or example:

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 the statements in the try clause, which avoids unexpected exceptions that excelt and don't catch.

Exception handling does not only deal with exceptions that occur directly in the try clause, but also in functions called in the clause (and even those called indirectly). 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

Throw an exception

Python uses the raise statement to throw a specified exception. For example:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere

The only argument for raise specifies the exception to be thrown. It must be an instance of an exception or a class of an exception (that is, a sub-class of Exception).

If you just want to know if this throws an exception and don't want to handle it, a simple raise statement can throw it again.

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

A user-defined exception

You can have your own exception by creating a new exception class. Exceptions should be inherited 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, class Exception defaults to __init__() overwritten.

Exception classes can do anything like any other class, but are generally simpler, provide only error-related properties, and allow code that handles exceptions to easily obtain this information.

When creating a module has the potential to throw many different exceptions, it is common practice to create a base exception class for the package, and then create different sub-classes for different error scenarios based on this base class:

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 exceptions end with "Error", just like standard exceptions.


Define cleanup behavior

The try statement also has an optional clause that defines the cleanup behavior that is performed in any case. For example:

>>> try:
        raise KeyboardInterrupt
    finally:
        print('Goodbye, world!')
   
Goodbye, world!
KeyboardInterrupt

The above example executes the final clause regardless of whether there is an exception in the try clause.

If an exception is thrown in the try clause (or in the except and else clauses) without any except intercepting it, the exception is thrown again after the final clause is executed.

Here's a more complex example (with the except and final 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'

Predefined cleanup behavior

Some objects define a standard cleanup behavior, regardless of whether the system has successfully used it, and once it is not needed, the standard cleanup behavior is performed.

This 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 this code is that when executed, the file remains open and is not closed.

The keyword with statement guarantees that an object such as a file will perform its cleanup correctly after it is used:

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

After the above code is executed, file f will always close even if something doesn't go wrong during processing.