0%
Exception Handling in Python

Exceptions

Anticipate and handle errors in your programs

10-15 min

Exception Handling in Python

Exceptions are events that occur during the execution of a program and disrupt the normal flow of instructions. In Python, exceptions are objects that represent errors. Exception handling is crucial for building robust applications that can cope with unexpected situations without crashing.

In this tutorial, we will explore how to identify, handle, and create exceptions in Python.

Why Handle Exceptions?

Exception handling matters for several reasons:

  1. Robustness: A program that handles exceptions properly can keep running even when errors occur.
  2. User experience: Display user-friendly error messages instead of technical tracebacks.
  3. Debugging: Get detailed information about the error to make it easier to fix.
  4. Resource management: Make sure resources (files, network connections, etc.) are properly released, even when an error occurs.

Common Exception Types in Python

Python includes many built-in exception types. Here are some of the most common:

  • SyntaxError: Error in the code syntax.
  • NameError: Attempt to use an undefined variable.
  • TypeError: Operation or function applied to an object of an inappropriate type.
  • ValueError: Operation or function receives an argument of the right type but with an inappropriate value.
  • IndexError: Attempt to access an index that does not exist in a sequence.
  • KeyError: Attempt to access a key that does not exist in a dictionary.
  • FileNotFoundError: Attempt to open a file that does not exist.
  • ZeroDivisionError: Division by zero.
  • ImportError: Failure when importing a module.
  • IOError: Input/output error (for example, when reading a file).
  • AttributeError: Attempt to access a nonexistent attribute.

The try-except-else-finally Structure

Python uses try, except, else, and finally blocks to handle exceptions.

Basic try-except Structure

try:
    # Code that may raise an exception
    resultat = 10 / 0
except ZeroDivisionError:
    # Code executed if a ZeroDivisionError occurs
    print("Erreur : Division par zéro !")

In this example, when 10 / 0 raises a ZeroDivisionError, the matching except block is executed.

Catching Multiple Exception Types

You can catch different types of exceptions with separate handlers:

try:
    # Code that may raise exceptions
    nombre = int(input("Entrez un nombre : "))
    resultat = 10 / nombre
except ValueError:
    # Executed if the input is not a valid number
    print("Erreur : Veuillez entrer un nombre valide.")
except ZeroDivisionError:
    # Executed if nombre == 0
    print("Erreur : La division par zéro n'est pas autorisée.")

You can also catch multiple exceptions with the same handler:

try:
    # Code that may raise exceptions
    nombre = int(input("Entrez un nombre : "))
    resultat = 10 / nombre
except (ValueError, ZeroDivisionError):
    # Executed if either of the listed exceptions occurs
    print("Erreur : Entrée invalide ou division par zéro.")

Catching All Exceptions

Although this is generally not recommended (because it can mask unexpected errors), you can catch all exceptions:

try:
    # Code that may raise any exception
    # ...
except Exception as e:
    # Executed for any exception that inherits from Exception
    print(f"Une erreur s'est produite : {e}")

The else Block

The else block is executed if no exception is raised in the try block:

try:
    nombre = int(input("Entrez un nombre : "))
    resultat = 10 / nombre
except ValueError:
    print("Entrée invalide.")
except ZeroDivisionError:
    print("Division par zéro.")
else:
    # This block is executed if no exception is raised
    print(f"Le résultat est {resultat}")

The finally Block

The finally block is executed no matter what, whether or not an exception is raised:

try:
    fichier = open("donnees.txt", "r")
    # File operations
except FileNotFoundError:
    print("Le fichier n'existe pas.")
finally:
    # This block is always executed
    # Ideal for releasing resources
    if 'fichier' in locals():
        fichier.close()
        print("Fichier fermé.")

The finally block is particularly useful for making sure resources are properly released, even when an error occurs.

Raising Exceptions

You can deliberately raise (or “throw”) exceptions using the raise statement:

def diviser(a, b):
    if b == 0:
        raise ZeroDivisionError("Division par zéro non autorisée")
    return a / b

try:
    resultat = diviser(10, 0)
except ZeroDivisionError as e:
    print(f"Erreur : {e}")

Creating Custom Exceptions

You can create your own exception types by inheriting from the Exception class or one of its subclasses:

class ValeurNegativeError(Exception):
    """Exception raised when a negative value is used where it should not be."""
    pass

def calculer_racine_carree(nombre):
    if nombre < 0:
        raise ValeurNegativeError("La racine carrée d'un nombre négatif n'est pas définie pour les nombres réels.")
    import math
    return math.sqrt(nombre)

try:
    resultat = calculer_racine_carree(-1)
except ValeurNegativeError as e:
    print(f"Erreur : {e}")

Assertions

Assertions are debugging checks that test whether a condition is true. If the condition is false, an AssertionError exception is raised:

def diviser(a, b):
    assert b != 0, "Le diviseur ne peut pas être zéro"
    return a / b

try:
    resultat = diviser(10, 0)
except AssertionError as e:
    print(f"Assertion échouée : {e}")

Assertions are mainly used for debugging and testing, and can be disabled in production mode.

Using the with Statement

The with statement is used to ensure that resources are properly managed, even when an exception occurs. It is a cleaner way to handle exceptions for operations that require resources such as files:

try:
    with open("fichier.txt", "r") as fichier:
        contenu = fichier.read()
        # The file is automatically closed at the end of the with block,
        # even if an exception is raised
except FileNotFoundError:
    print("Le fichier n'existe pas.")

Exception Hierarchy

Exceptions in Python form a hierarchy. Here is a simplified portion of that hierarchy:

  • BaseException
    • Exception
      • ArithmeticError
        • ZeroDivisionError
        • OverflowError
      • LookupError
        • IndexError
        • KeyError
      • OSError
        • FileNotFoundError
        • PermissionError
      • TypeError
      • ValueError
      • …
    • SystemExit
    • KeyboardInterrupt
    • …

When catching exceptions, order matters. If you have several except blocks, Python checks them in order. If an exception is a subclass of another exception that you have already caught, it will never be reached:

try:
    # Code that may raise exceptions
    # ...
except Exception:
    # This block will catch all exceptions that inherit from Exception
    print("Une exception s'est produite")
except ZeroDivisionError:
    # This block will NEVER be reached because ZeroDivisionError is a subclass of Exception
    print("Division par zéro")

To avoid this problem, place the most specific exceptions before the more general ones.

Best Practices for Exception Handling

  1. Be specific: Catch specific exceptions rather than using a generic except.

  2. Don’t silently swallow errors: Avoid catching an exception without handling it properly or leaving a trace.

  3. Use finally for cleanup: Use the finally block to make sure resources are properly released.

  4. Create custom exceptions when appropriate: This can make your code more readable and allow finer-grained error handling.

  5. Keep try blocks small: Put only the code that may raise the exception in the try block.

  6. Log exceptions: In production applications, consider recording exceptions in log files.

Conclusion

Effective exception handling is an essential skill for any Python programmer. It lets you write robust programs that can handle errors gracefully.

In this tutorial, we explored how to identify, catch, and raise exceptions, how to create your own exception types, and how to use the with statement to manage resources. We also discussed best practices for effective exception handling.

In the next tutorial, we will cover file manipulation in Python, an area where exception handling is particularly important.

Comments

Comments are powered by GitHub Discussions

Sign in with GitHub to join the discussion

Link copied!