Python Tutorial (29) - Errors and Exceptions

Time: Column:Python views:236

Common Errors for Python Beginners

When starting with Python programming, beginners often encounter error messages. In this chapter, we will focus on these errors, which were not covered previously.

Python has two easily recognizable types of errors: Syntax Errors and Exceptions.

Python Assert Statement

Python's assert is used to evaluate an expression. If the expression evaluates to false, it raises an exception.

Python Tutorial (29) - Errors and Exceptions

Syntax Errors

Syntax errors, also known as parsing errors, are common among beginners. Here's an example:

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

In this example, the print() function is missing a colon (:) before it.

The syntax parser points out the line with the error and uses a small arrow to indicate where the error occurred.


Exceptions

Even if the syntax of a Python program is correct, errors can still occur while the program is running. These errors are called exceptions.

Most exceptions are not handled by the program and show up as error messages, as seen below:

>>> 10 * (1/0)  # Division by zero, triggers an exception
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero

>>> 4 + spam*3  # 'spam' is not defined, triggers an exception
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined

>>> '2' + 2  # Cannot concatenate int and 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 are displayed with different types: ZeroDivisionErrorNameError, and TypeError are examples shown here.

The first part of the error message displays the context in which the exception occurred, showing detailed information in the form of a traceback.


Exception Handling

try/except

You can catch exceptions using the try/except statement.

Python Tutorial (29) - Errors and Exceptions

In the following example, the user is asked to input a valid integer. If the user interrupts the program (e.g., using Control-C or another OS signal), a KeyboardInterrupt exception is raised.

while True:
    try:
        x = int(input("Please enter a number: "))
        break
    except ValueError:
        print("That's not a number! Please try again.")

Here's how the try statement works:

  • First, the code inside the try block is executed.

  • If no exception occurs, the except block is ignored, and the code continues after the try block.

  • If an exception occurs during the try block, the remaining code in the block is skipped, and if the exception matches the type specified in except, that block is executed.

  • If no except block matches, the exception is propagated up.

try statement can have multiple except blocks to handle different types of exceptions, but only one will be executed.

You can also handle multiple exceptions in one block by enclosing them in parentheses:

except (RuntimeError, TypeError, NameError):
    pass

The last except block can ignore the exception name and act as a wildcard. You can print an error message and re-raise the exception:

import sys

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

try/except...else

The try/except statement can also have an optional else clause, which must appear after all except blocks. This clause is executed only if no exceptions were raised in the try block.

Python Tutorial (29) - Errors and Exceptions

Example:

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

Using an else clause is better than putting all code inside the try block since it avoids catching exceptions that weren't meant to be caught.


Handling Exceptions in Functions

Exception handling works not only for exceptions in the try block but also for exceptions that occur in functions called within the try block. For example:

def this_fails():
    x = 1/0

try:
    this_fails()
except ZeroDivisionError as err:
    print(f'Handling run-time error: {err}')

Output:

Handling run-time error: division by zero

This overview helps beginners understand how to recognize and handle common errors in Python programming, making code more robust and maintainable.


try-finally Statement

The try-finally statement ensures that the final code block is executed, regardless of whether an exception occurs.

1Python Tutorial (29) - Errors and Exceptions

In the following example, the finally block will always be executed, whether or not an exception is raised:

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 line will always execute, whether an exception occurred or not.')

Raising Exceptions

Python uses the raise statement to trigger a specified exception.

The syntax for raise is as follows:

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

Python Tutorial (29) - Errors and Exceptions

In the following example, an exception is raised 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))

Running this 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 only argument to raise specifies the exception to be raised. It must be an instance of an exception or a subclass of Exception.

If you want to check whether an exception has been raised but don’t need to handle it, a simple raise statement can be used to re-raise the exception:

>>> try:
        raise NameError('HiThere')  # Simulate an exception.
    except NameError:
        print('An exception flew by!')
        raise

This outputs:

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

User-Defined Exceptions

You can define your own exceptions by creating a new exception class. These 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)

Output:

My exception occurred, value: 4

You can also raise a user-defined exception like this:

>>> raise MyError('oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

In this example, the default __init__() method from the Exception class has been overridden.

When a module is likely to raise different exceptions, it’s common practice to create a base exception class for that module, and then define subclasses for specific error conditions:

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 is not allowed.

    Attributes:
        previous -- state before the transition
        next -- attempted new state
        message -- explanation of why the 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 exceptions.


Defining Cleanup Behavior

The try statement has an optional clause that defines cleanup actions, which are executed no matter what. For example:

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

Output:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
KeyboardInterrupt

In this example, the finally clause is executed whether or not an exception is raised in the try block.

If an exception is raised in the try block (or in an except or else block) and isn’t handled by any except clause, the exception is re-raised after the finally block is executed.


Here's a more complex example that includes 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'

Predefined Cleanup Actions

Some objects define standard cleanup actions that are automatically executed when the object is no longer needed, regardless of whether the operation was successful.

The following example demonstrates opening a file and printing its contents:

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

However, this code leaves the file open after it finishes executing.

The with statement ensures that objects like files are properly cleaned up after use, even if an error occurs:

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

Once the with block is exited, the file f is automatically closed, even if an error occurs during the process.