How to Implement Error Handling in Python with Try-Except Blocks
Tags: python, exceptions
Published: 2023-09-26 14:39:11

Errors can be both annoying and frustrating. Often, these exceptions occur due to incorrect implementation or usage.

As programmers, it is crucial to anticipate exceptions and handle them correctly. Throughout this lesson, we will explore the most common exceptions encountered and use practical examples to explain the fundamentals of error handling. 

## Prerequisites

- A working knowledge of Python programming
- Python installed on your system 
- A code editor
- A willingness to learn and experiment with Python error-handling 

## Basics of Try-Except
The `try-except` block is used to handle exceptions. Let us take a look at the `try` and `except` blocks and explore how they work.

### Try block
The try block is where you enclose the code that you anticipate might raise an error.

### Except block 
This block handles the exception. 

### Try-Except 

```
try:
    # Code that may raise an error
except:
    # Handle the exception, e.g., print an error message
```

## Handling different types of exceptions
A try block can have several exceptions to handle specific errors. You could also use the `Exception` block to generalize every error.

```
try:
   pass
except Exception:
    # do something
    pass
```

There are also cases where you want to print the error message instead of a different message in the output.

```
try:
    result = '5' + 2
    print(result)
except TypeError as e:
    print('error', e)

```
Result in the terminal:
_error can only concatenate str (not "int") to str_

**How do you know what specific error to handle?**
Simply run the code without an error handler; whatever error it throws at the terminal, you want to use it in the except block.

Several errors you could encounter are:

- Syntax error
- NameError
- IndexError
- KeyError
- ZeroDivisionError
- TypeError
- ValueError
- ImportError
- FileNotFoundError
- OsError
- AttributeError

If you want to read more about these errors check the [documentation](https://docs.python.org/3/tutorial/errors.html). 

### Using specific exception types
**Step 1**: Concatenating a string and an integer without a try-except block.
`print('a' + 5)`

Result in the Terminal:
_print('a' + 5)
TypeError: can only concatenate str (not "int") to str_

**Step 2**: Now that we have identified the exception, let’s use a try-except block.

```
try:
    result = 'a' + 5
    print(result)
except TypeError:  
     print('invalid operation')
```

Result in the terminal: _invalid operation_

### Using multiple except blocks 
In this example, we have a try block that contains code that may raise exceptions. We have multiple exception blocks to handle specific exception types.

```
try:
    # Code that may raise exceptions
    num = int(input("Enter a number: "))
    result = 10 / num
    fruits = ["apple", "banana", "cherry"]
    index = int(input("Enter an index to access a fruit: "))
    print(f"The selected fruit is: {fruits[index]}")
except ValueError:
    # Handle a ValueError (e.g., when user input can't be converted to int)
    print("Invalid input. Please enter a valid number.")
except ZeroDivisionError:
    # Handle a ZeroDivisionError (e.g., when dividing by zero)
    print("Division by zero is not allowed.")
except IndexError:
    # Handle an IndexError (e.g., when accessing an index outside the list range)
    print("Invalid index. Please choose a valid index.")
except Exception as e:
    # Handle other exceptions not explicitly caught above
    print(f"An error occurred: {e}")

```

### The else block
The else block is useful for executing code when no exceptions occur, providing a way to separate the normal code flow from exception handling logic.

```
try:
    # Code that may or may not raise an exception
    num1 = int(input("Enter the first number: "))
    num2 = int(input("Enter the second number: "))
    result = num1 / num2
except ZeroDivisionError:
    # Handle division by zero exception
    print("Division by zero is not allowed.")
except ValueError:
    # Handle invalid input (non-integer) exception
    print("Invalid input. Please enter valid numbers.")
else:
    # Code to execute if no exceptions are raised
    print(f"The result of the division is: {result}")

```

### Using the 'finally' block for cleanup
Regardless of whether an exception is raised or not, the code within the finally block is executed. 
This can be used for closing files or network connections.

```
try:
    # Code that may or may not raise an exception
    file = open("example.txt", "r")
    data = file.read()
except FileNotFoundError:
    # Handle file not found exception
    print("File not found.")
except Exception as e:
    # Handle other exceptions
    print(f"An error occurred: {e}")
finally:
    # Code that always executes, regardless of exceptions
    print("Cleaning up resources...")
    file.close()  # Close the file to release resources
```

## Custom Exception Handling 

### Raising exceptions with the 'raise' keyword
Raising exceptions is important for signaling errors and exceptional conditions in your code.

```
def some_function():
    # Some condition that warrants raising an exception
    raise Exception("This is a custom exception message.")

try:
    some_function()
except Exception as e:
    print(f"Exception caught: {e}")
```


**Example**: Imagine you are writing a program that calculates the square root of a number provided by the user. You want to ensure that the user enters a valid positive number because the square root of negative numbers is undefined in the real number system.

```
import math

def calculate_square_root(number):
    if number < 0:
        raise ValueError("Invalid input: Please enter a positive number.")
    return math.sqrt(number)

try:
    user_input = float(input("Enter a number: "))
    result = calculate_square_root(user_input)
    print(f"The square root of {user_input} is {result}")
except ValueError as e:
    print(f"Error: {e}")
```

Result 1:
Enter a number: -2
Error: Invalid input: Please enter a positive number.
Result 2:
Enter a number: re
Error: could not convert string to float: 're'

### Exercise
In programming you can only familiarize yourself with these concepts through practice, look things up online, and try things out. For the exceptions that weren’t covered in this article, purposely create some issues. For example, if it were a syntax error you could write a function without a colon or maybe a variable without the equal sign. For `NameError` you could print out a variable name that doesn’t exist. In the case of an `importError`, you could try importing a module that doesn’t exist.

### Mistakes to avoid when handling exceptions

- The error message should be specific and easily understood.

```
try:
    user_input = int(input('Enter a number: '))
except ValueError as e:
   print('An error has occurred')
```

**Why is it a problem?**
What error has occurred? This message is confusing to both the user and the developer. Imagine this was a program with several thousands of lines of code, and you are trying to debug. With this kind of message, it makes debugging harder. Picture a user using this program. They will feel frustrated  because the error message doesn’t explain clearly what they did wrong. 

**Better way to write it:**

```
try:
    user_input = int(input('Enter a number: '))
except ValueError as e:
    print('Enter a valid integer')
    print(e)
```

- Relying on exceptions to control program flow. For example, using exceptions for if-else logic.

```
try:
    result = num / 2
    if num <= 0:
        raise ValueError(f'{num} must be greater than zero')
    print(result)
except ValueError as e:
    print(e)
```
	
**Why is it a problem?**
A simple if-else block would have solved it. Always remember that simplicity is preferable to writing complex code.

**Better way to write it:**

```
num = 5

if num > 0:
    print(num / 2)
else:
    print(f'{num} must be greater than zero')
```

- Placing large sections of code within a `try` block.

```
try:
    num1 = int(input("Enter the first number: "))
    num2 = int(input("Enter the second number: "))
    result = num1 / num2
    with open("sample1.txt", "r") as file:
        content = file.read()
except Exception as e:
    print(f"An error occurred: {e}")
```

**Why is it a problem?**
You got a user input and performed a division which is okay. Opening a file within the `try` block is wrong. A better approach would be to remove the code opening a file.
	

## Conclusion
We can draw the following conclusion from this lesson:

- Handling exceptions prevents your program from crashing unexpectedly.
- We can use multiple except blocks.
- The error message should be easily understood.
- Raise exceptions wisely.

If you enjoyed reading this, you just need to do this. Follow me on [Twitter](https://twitter.com/PraiseID3?s=09) and [LinkedIn](https://www.linkedin.com/in/praise-ifeoluwa-idowu-back-end-developer-django/).