Exceptions
Anticipate and handle errors in your programs
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:
- Robustness: A program that handles exceptions properly can keep running even when errors occur.
- User experience: Display user-friendly error messages instead of technical tracebacks.
- Debugging: Get detailed information about the error to make it easier to fix.
- 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:
BaseExceptionExceptionArithmeticErrorZeroDivisionErrorOverflowError
LookupErrorIndexErrorKeyError
OSErrorFileNotFoundErrorPermissionError
TypeErrorValueError- …
SystemExitKeyboardInterrupt- …
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
-
Be specific: Catch specific exceptions rather than using a generic
except. -
Don’t silently swallow errors: Avoid catching an exception without handling it properly or leaving a trace.
-
Use
finallyfor cleanup: Use thefinallyblock to make sure resources are properly released. -
Create custom exceptions when appropriate: This can make your code more readable and allow finer-grained error handling.
-
Keep try blocks small: Put only the code that may raise the exception in the
tryblock. -
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