normal

Exceptions in Python

Consider exceptions in Python as those unexpected visitors that pop up while a program is diligently executing its tasks.

You may ask, 'How do these so-called exceptions differ from errors?' Good question. Simply put, errors are self-inflicted wounds; they stem from issues embedded within the code that cause your program to crash during compilation (like a syntax error). Exceptions, on the other hand, are unforeseen circumstances that can halt the program during execution unless we intervene and handle them properly.

That's where Python comes to our rescue. It equips us with the mighty try-except armor that allows us to gracefully handle these exceptions and keep our program running without a hitch.

Example of Exception

Take a look at this snippet:

  1. x=1
  2. y=0
  3. z=x/y
  4. print(z)

This piece of code is, by all means, a well-structured Pythonic verse, devoid of syntax errors.

But wait. There's a catch in the third line. We are attempting a division by zero, which, as your high school math teacher would agree, is a big no-no. Because y equals zero.

Python, being the vigilant sentinel it is, throws a "ZeroDivisionError:" exception and brings the execution to a halt.

Traceback (most recent call last):
File "/home/main.py", line 3, in <module>
z=x/y
ZeroDivisionError: division by zero

Unhandled exceptions cause the program to halt and display an error message comprising two parts:

  • The traceback, which indicates the specific line of code where the exception occurred.
  • The type of exception that triggered the error (e.g., ZeroDivisionError, IndexError, etc.). Different types of exceptions are instances of subclasses derived from the built-in BaseException class.

To outmaneuver this roadblock, we use Python's try-except construct.

  1. try:
  2. x=1
  3. y=0
  4. z=x/y
  5. print(z)
  6. except ZeroDivisionError:
  7. print("Oops! You tried to divide by zero!")

In this scenario, as soon as Python senses the ZeroDivisionError exception, it leaps into action and executes the block of code nestled under the "except" clause. This ensures that the execution of the program doesn't break off abruptly.

Thus, the program courteously informs you:

Oops! You tried to divide by zero

To put it in a nutshell, Python empowers you with the ability to catch and tactfully manage exceptions the moment they raise their heads.

Other specimens of exceptions include the "TypeError", which rears up when you try to pull off an operation on a non-compatible data type, or the "FileNotFoundError", that's triggered when you're trying to pry open a file that doesn't exist. Python also leaves the door open for you to craft your own custom exceptions, which can be accomplished by creating a new class derived from the Exception base class.

Handling Generic Exceptions

Python allows you to catch all exceptions using a generic `except` clause without specifying the error type.

In this way, any error that occurs will be intercepted.

Here's a practical example:

import sys

while True:
    try:
        i = int(input("Enter an integer: "))
        print(10 / i)
    except:
        error_type, error_instance, _ = sys.exc_info()
        print(f"An unexpected error occurred... Error type: {error_type}, Cause: {error_instance}")
        print('The program continues to run normally')

In this example, if an exception occurs during execution, the type and cause of the error will be printed, and the program will continue to prompt the user to enter an integer.

This approach allows the program to handle all exceptions in a general manner, ensuring it doesn't stop and still provides useful information to the user or for debugging purposes.

When using generic exceptions, the sys.exc_info() function is extremely helpful as it captures the cause of the error, allowing you to understand what went wrong without interrupting the program's execution.

Except as Clause

The except as clause allows you to assign an exception to a variable.

except as

This enables you to utilize the information stored in the variable within your code.

For example, you can use except with as to catch specific exceptions and provide meaningful feedback to the user.

Consider a scenario where you want to divide two numbers entered by the user and handle any potential errors, such as division by zero or entering a non-numeric value.

while True:
    try:
        num1 = float(input("Enter the first number: "))
        num2 = float(input("Enter the second number: "))
        result = num1 / num2
        print("The result of the division is:", result)
        break
    except ValueError as ex:
        print("Error: Non-numeric value entered. Please try again.", ex)
    except ZeroDivisionError as ex:
        print("Error: Division by zero. Please try again.", ex)

This code includes two except as clauses:

  • The except ValueError as ex clause catches the 'ValueError' exception, assigns it to the variable 'ex', and then prints an error message displaying the error. This occurs, for example, when the user inputs a non-numeric value.
  • The except ZeroDivisionError as ex clause catches the 'ZeroDivisionError' exception and prints a different custom error message. This happens, for example, when the user attempts to divide by zero.

If you input the values 10 and 2, Python calculates and returns the result of the division.

The result of the division is: 5.0

However, if you enter the values 10 and 'a', the Python code responds with "Error: Non-numeric value entered. Please try again."

It then displays the content of the variable "ex," which in this case is "could not convert string to float: 'a'."

Error: Non-numeric value entered. Please try again. could not convert string to float: 'a'

Finally, if you input the numbers 10 and 0, the output is another custom message.

Error: Division by zero. Please try again. float division by zero

This straightforward example demonstrates how to handle common exceptions when a program

Finally Clause

The finally clause lets you define a block of code that will run after the try block and any except blocks, no matter if an exception was raised or not.

try:
    # Code that might raise an exception
    pass
except SomeException as e:
    # Code to handle the exception
    pass
finally:
    # Code that will always run, regardless of an exception
    pass

This is particularly useful for releasing resources or ensuring certain operations are completed even if something goes wrong.

For example, consider a program that needs to open a file, read its contents, and then close it. Various issues could occur while opening or reading the file: the file might not exist, or you might not have permission to read it. Even with these issues, it is crucial to always close the file to prevent resource leaks.

Let's look at a practical example to understand how this works.

try:
    file = open('example.txt', 'r')
    print(file.read())
except FileNotFoundError:
    print("Error: The file does not exist.")
finally:
    print("This always runs.")
    try:
        file.close()
    except NameError:
        pass

The first try block attempts to open and read a file.

If the file does not exist, the except block prints an error message.

Finally, the finally block prints a message and closes the file, ignoring the error if the file was not opened.

In this way, the finally clause ensures the file is always closed, regardless of whether the file was read successfully or not.

The finally clause is particularly useful for releasing resources, such as closing files, network connections, databases, etc. It ensures that certain operations are always performed, even in case of errors.

Else Clause

The else clause is an optional feature that allows you to execute code only if no exceptions are raised within the try block.

try:
    # code that might raise an exception
except ExceptionType:
    # code to handle the exception
else:
    # code that runs if no exception is raised
finally:
    # code that runs regardless of whether an exception was raised or not

You must place the else clause after all except clauses and before the finally clause, if present.

This clause is particularly useful for running code that should only execute in the absence of exceptions.

For instance, imagine you want to perform a simple division and handle the case where the denominator is zero, which would raise a ZeroDivisionError exception.

try:
    # Attempt to perform the division
    numerator = 10
    denominator = 2
    result = numerator / denominator
except ZeroDivisionError:
    # This code runs if a division by zero occurs
    print("Error: division by zero.")
else:
    # This code runs only if no exception occurs
    print(f"The result of the division is: {result}")
finally:
    # This code runs regardless of whether an exception occurred or not
    print("End of division operation.")

In this example, each block serves a specific purpose:

  • The try block attempts to perform the division 10 / 2.
  • The except block runs only if a ZeroDivisionError exception occurs.
  • The else block runs only if the try block does not raise any exceptions. In this case, it prints the result of the division.
  • The finally block runs regardless of whether an exception occurs or not. In this example, it prints a message indicating that the division operation is complete.

In this case, the denominator is 2, and the output result is:

The result of the division is: 5.0
End of division operation.

If the denominator had been zero (denominator=0), the output would have been:

Error: division by zero.
End of division operation.

This straightforward example clearly demonstrates how the else clause functions within exception handling.

Raising an Exception

To raise an exception in Python, use the raise keyword.

raise ExceptionType("Error message")

The exception type and message within the parentheses are optional.

The raise keyword allows you to manually trigger an exception in specific situations, even when Python wouldn't automatically raise one.

This enables you to handle errors or special conditions in your code in a controlled manner.

For example, suppose you want to raise an exception when a user inputs a negative number:

def check_positive_number(number):
    if number < 0:
        raise ValueError("Number cannot be negative!")
    return number

try:
    check_positive_number(-5)
except ValueError as e:
    print(f"Error: {e}")

The check_positive_number function verifies if the number provided is positive.

In this case, since the number is negative (-5), a ValueError exception is raised.

Error: Number cannot be negative!

Propagating an Exception

Propagating an exception means letting the exception pass through the call stack levels until it is either handled or causes the program to terminate.

In other words, instead of handling the exception where it occurs, you allow it to bubble up to higher levels in the call stack.

Here's a practical example:

def func_c():
    print("In func_c")
    raise ValueError("Error in func_c")

def func_b():
    print("In func_b")
    func_c()  # Call to func_c

def func_a():
    print("In func_a")
    func_b()  # Call to func_b

try:
    func_a()  # Call to the main function
except ValueError as e:
    print(f"Exception caught in the main block: {e}")

In this code, the function func_a() is called within the main try-except block.

The function func_a() calls func_b(), which in turn calls func_c().

In func_c(), a ValueError exception is raised, which propagates through the calling functions until it is caught in the initial try-except block.

In func_a
In func_b
In func_c
Exception caught in the main block: Error in func_c

This straightforward example illustrates how an exception can propagate through multiple function calls and eventually be handled in the main block of the program.




Report a mistake or post a question




FacebookTwitterLinkedinLinkedin