Python Programming for Beginners

Chapter 15: Python Exception Handling – try, except, else and finally

<h2>15.1 Introduction to Exception Handling</h2>

<p>
Exception handling is used to manage errors that may occur while a Python
program is running. Instead of allowing the program to stop suddenly,
we can handle certain errors and provide a meaningful message to the user.
</p>

<p>
Python provides <code>try</code>, <code>except</code>, <code>else</code>,
and <code>finally</code> blocks for handling exceptions.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))
   print(number)

except ValueError:
   print("Please enter a valid number.")</code></pre>


<h2>15.2 What is an Exception?</h2>

<p>
An exception is an error that occurs during program execution and
interrupts the normal flow of a program.
</p>

<p>
For example, trying to divide a number by zero produces an exception.
</p>

<pre><code>number = 10
result = number / 0

print(result)</code></pre>

<p>
The above program produces a <code>ZeroDivisionError</code>.
</p>


<h2>15.3 Why is Exception Handling Important?</h2>

<p>
Exception handling makes programs more reliable and user-friendly.
It allows a program to respond to unexpected situations without
crashing unnecessarily.
</p>

<ul>
   <li>Prevents sudden program termination</li>
   <li>Provides meaningful error messages</li>
   <li>Improves program reliability</li>
   <li>Helps handle invalid user input</li>
   <li>Makes debugging easier</li>
</ul>


<h2>15.4 The try Block</h2>

<p>
The <code>try</code> block contains code that may produce an exception.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

   print("You entered:", number)</code></pre>

<p>
If an exception occurs inside the <code>try</code> block, Python looks
for a suitable <code>except</code> block.
</p>


<h2>15.5 The except Block</h2>

<p>
The <code>except</code> block is used to handle an exception.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

except ValueError:
   print("Invalid input.")</code></pre>


<h2>15.6 Handling ValueError</h2>

<p>
A <code>ValueError</code> can occur when a function receives a value
of the correct type but an inappropriate value.
</p>

<pre><code>try:
   age = int(input("Enter your age: "))

   print("Age:", age)

except ValueError:
   print("Age must be a number.")</code></pre>


<h2>15.7 Handling ZeroDivisionError</h2>

<pre><code>try:
   number = 20
   divisor = 0

   result = number / divisor

   print(result)

except ZeroDivisionError:
   print("A number cannot be divided by zero.")</code></pre>


<h2>15.8 Handling IndexError</h2>

<p>
An <code>IndexError</code> occurs when you try to access an invalid
index of a sequence.
</p>

<pre><code>numbers = [10, 20, 30]

try:
   print(numbers[5])

except IndexError:
   print("Index is outside the list.")</code></pre>


<h2>15.9 Handling KeyError</h2>

<p>
A <code>KeyError</code> can occur when a dictionary key does not exist.
</p>

<pre><code>student = {
   "name": "Aman",
   "marks": 88
}

try:
   print(student["age"])

except KeyError:
   print("The requested key does not exist.")</code></pre>


<h2>15.10 Handling TypeError</h2>

<p>
A <code>TypeError</code> can occur when an operation is performed on
incompatible types.
</p>

<pre><code>try:
   result = "10" + 5

   print(result)

except TypeError:
   print("The two values have incompatible types.")</code></pre>


<h2>15.11 Multiple except Blocks</h2>

<p>
A program can use multiple <code>except</code> blocks to handle
different types of exceptions.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))
   result = 100 / number

   print(result)

except ValueError:
   print("Please enter a valid number.")

except ZeroDivisionError:
   print("Cannot divide by zero.")</code></pre>


<h2>15.12 Using a General Exception</h2>

<p>
The <code>Exception</code> class can be used to catch many common
runtime exceptions.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

   print(100 / number)

except Exception as error:
   print("An error occurred:", error)</code></pre>

<p>
Specific exceptions are generally preferable when you know what kind
of error you want to handle.
</p>


<h2>15.13 The else Block</h2>

<p>
The <code>else</code> block runs when no exception occurs in the
<code>try</code> block.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

except ValueError:
   print("Invalid number.")

else:
   print("You entered:", number)</code></pre>


<h2>15.14 try, except and else Together</h2>

<pre><code>try:
   number = int(input("Enter a number: "))
   result = 100 / number

except ValueError:
   print("Please enter a valid integer.")

except ZeroDivisionError:
   print("Zero is not allowed.")

else:
   print("Result:", result)</code></pre>


<h2>15.15 The finally Block</h2>

<p>
The <code>finally</code> block runs after the <code>try</code> and
<code>except</code> processing, whether or not an exception occurs.
It is commonly used for cleanup operations.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

except ValueError:
   print("Invalid input.")

finally:
   print("Program finished.")</code></pre>


<h2>15.16 Complete Exception Handling Structure</h2>

<p>
A complete exception-handling structure can contain all four blocks.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

except ValueError:
   print("Invalid input.")

else:
   print("Number:", number)

finally:
   print("Execution completed.")</code></pre>


<h2>15.17 Example with Division</h2>

<pre><code>try:
   number = int(input("Enter a number: "))
   divisor = int(input("Enter divisor: "))

   result = number / divisor

except ValueError:
   print("Please enter valid integers.")

except ZeroDivisionError:
   print("Divisor cannot be zero.")

else:
   print("Result:", result)

finally:
   print("Thank you for using the program.")</code></pre>


<h2>15.18 Raising an Exception</h2>

<p>
The <code>raise</code> statement can be used to deliberately generate
an exception when a specific condition is not acceptable.
</p>

<pre><code>age = 15

if age < 18:
   raise ValueError("Age must be 18 or above.")</code></pre>


<h2>15.19 Raising a ValueError</h2>

<pre><code>def check_marks(marks):
   if marks < 0 or marks > 100:
       raise ValueError("Marks must be between 0 and 100.")

   return marks


print(check_marks(85))</code></pre>


<h2>15.20 Handling a Raised Exception</h2>

<pre><code>def check_age(age):
   if age < 18:
       raise ValueError("Age must be 18 or above.")

   return True


try:
   check_age(16)

except ValueError as error:
   print(error)</code></pre>


<h2>15.21 Using Exception Objects</h2>

<p>
You can store the exception object in a variable using the
<code>as</code> keyword.
</p>

<pre><code>try:
   number = int("abc")

except ValueError as error:
   print("Error:", error)</code></pre>


<h2>15.22 Nested try Blocks</h2>

<p>
A <code>try</code> block can be placed inside another <code>try</code>
block when different levels of error handling are required.
</p>

<pre><code>try:
   number = int(input("Enter a number: "))

   try:
       result = 100 / number
       print(result)

   except ZeroDivisionError:
       print("Cannot divide by zero.")

except ValueError:
   print("Invalid input.")</code></pre>


<h2>15.23 Exception Handling with Lists</h2>

<pre><code>numbers = [10, 20, 30]

try:
   index = int(input("Enter index: "))

   print(numbers[index])

except ValueError:
   print("Enter a valid integer index.")

except IndexError:
   print("That index does not exist.")</code></pre>


<h2>15.24 Exception Handling with Dictionaries</h2>

<pre><code>student = {
   "name": "Aman",
   "marks": 88
}

try:
   key = input("Enter key: ")

   print(student[key])

except KeyError:
   print("Key not found.")</code></pre>


<h2>15.25 Exception Handling with Functions</h2>

<p>
Exception handling can be used inside functions to make reusable
program components safer.
</p>

<pre><code>def divide(a, b):
   try:
       return a / b

   except ZeroDivisionError:
       return "Cannot divide by zero."


print(divide(20, 4))
print(divide(20, 0))</code></pre>

<p>Output:</p>

<pre><code>5.0
Cannot divide by zero.</code></pre>


<h2>15.26 Returning an Error Message</h2>

<pre><code>def convert_number(value):
   try:
       return int(value)

   except ValueError:
       return "Invalid number."


print(convert_number("25"))
print(convert_number("hello"))</code></pre>

<p>Output:</p>

<pre><code>25
Invalid number.</code></pre>


<h2>15.27 Input Validation</h2>

<p>
Exception handling is frequently used to validate information entered
by users.
</p>

<pre><code>while True:
   try:
       age = int(input("Enter your age: "))

       if age < 0:
           print("Age cannot be negative.")
           continue

       break

   except ValueError:
       print("Please enter a number.")

print("Your age is:", age)</code></pre>


<h2>15.28 Handling Invalid Marks</h2>

<pre><code>try:
   marks = float(input("Enter marks: "))

   if marks < 0 or marks > 100:
       raise ValueError("Marks must be between 0 and 100.")

   print("Marks:", marks)

except ValueError as error:
   print("Invalid marks:", error)</code></pre>


<h2>15.29 File Handling with Exception Handling</h2>

<p>
Exception handling is especially useful when working with files because
the requested file may not exist.
</p>

<pre><code>try:
   with open("student.txt", "r") as file:
       content = file.read()

   print(content)

except FileNotFoundError:
   print("Student file was not found.")</code></pre>


<h2>15.30 File Cleanup with finally</h2>

<pre><code>file = None

try:
   file = open("example.txt", "r")

   print(file.read())

except FileNotFoundError:
   print("File not found.")

finally:
   if file is not None:
       file.close()

   print("File operation completed.")</code></pre>


<h2>15.31 Using finally for Cleanup</h2>

<p>
The <code>finally</code> block is useful for operations that should
happen regardless of whether an exception occurs.
</p>

<pre><code>try:
   print("Opening resource")
   number = 10 / 2

except ZeroDivisionError:
   print("Division error.")

finally:
   print("Cleaning up resource.")</code></pre>


<h2>15.32 Custom Exception Classes</h2>

<p>
Python allows you to create your own exception classes by inheriting
from <code>Exception</code>.
</p>

<pre><code>class InvalidMarksError(Exception):
   pass


marks = 120

if marks > 100:
   raise InvalidMarksError("Marks cannot be greater than 100.")</code></pre>


<h2>15.33 Handling a Custom Exception</h2>

<pre><code>class InvalidMarksError(Exception):
   pass


try:
   marks = 120

   if marks > 100:
       raise InvalidMarksError("Invalid marks.")

except InvalidMarksError as error:
   print(error)</code></pre>


<h2>15.34 Custom Exception with a Function</h2>

<pre><code>class InsufficientBalanceError(Exception):
   pass


def withdraw(balance, amount):
   if amount > balance:
       raise InsufficientBalanceError("Insufficient balance.")

   return balance - amount


try:
   balance = withdraw(1000, 1500)

   print("Remaining balance:", balance)

except InsufficientBalanceError as error:
   print(error)</code></pre>


<h2>15.35 Exception Chaining</h2>

<p>
Sometimes one exception occurs while handling another exception.
Python allows related exceptions to be connected using
<code>from</code>.
</p>

<pre><code>try:
   number = int("abc")

except ValueError as error:
   raise RuntimeError("Unable to process the number.") from error</code></pre>


<h2>15.36 Assertions</h2>

<p>
The <code>assert</code> statement can be used to check whether a
condition is true during program execution.
</p>

<pre><code>age = 20

assert age >= 18

print("Age is valid.")</code></pre>


<h2>15.37 Assertion Error</h2>

<pre><code>age = 15

assert age >= 18, "Age must be at least 18"

print("Age is valid.")</code></pre>


<h2>15.38 Difference Between Error and Exception</h2>

<table>
   <thead>
       <tr>
           <th>Term</th>
           <th>Meaning</th>
       </tr>
   </thead>
   <tbody>
       <tr>
           <td>Error</td>
           <td>A problem that prevents or disrupts normal program execution.</td>
       </tr>
       <tr>
           <td>Exception</td>
           <td>An event that occurs during execution and can often be handled.</td>
       </tr>
   </tbody>
</table>


<h2>15.39 Common Python Exceptions</h2>

<table>
   <thead>
       <tr>
           <th>Exception</th>
           <th>Common Cause</th>
       </tr>
   </thead>
   <tbody>
       <tr>
           <td><code>ValueError</code></td>
           <td>Invalid value for an operation</td>
       </tr>
       <tr>
           <td><code>TypeError</code></td>
           <td>Incompatible data types</td>
       </tr>
       <tr>
           <td><code>ZeroDivisionError</code></td>
           <td>Division by zero</td>
       </tr>
       <tr>
           <td><code>IndexError</code></td>
           <td>Invalid sequence index</td>
       </tr>
       <tr>
           <td><code>KeyError</code></td>
           <td>Missing dictionary key</td>
       </tr>
       <tr>
           <td><code>FileNotFoundError</code></td>
           <td>Requested file does not exist</td>
       </tr>
       <tr>
           <td><code>PermissionError</code></td>
           <td>Insufficient permission</td>
       </tr>
       <tr>
           <td><code>NameError</code></td>
           <td>Undefined variable or name</td>
       </tr>
       <tr>
           <td><code>AttributeError</code></td>
           <td>Invalid attribute access</td>
       </tr>
   </tbody>
</table>


<h2>15.40 try vs except vs else vs finally</h2>

<table>
   <thead>
       <tr>
           <th>Block</th>
           <th>Purpose</th>
       </tr>
   </thead>
   <tbody>
       <tr>
           <td><code>try</code></td>
           <td>Contains code that may raise an exception</td>
       </tr>
       <tr>
           <td><code>except</code></td>
           <td>Handles an exception</td>
       </tr>
       <tr>
           <td><code>else</code></td>
           <td>Runs when no exception occurs</td>
       </tr>
       <tr>
           <td><code>finally</code></td>
           <td>Runs after the exception-handling process</td>
       </tr>
   </tbody>
</table>


<h2>15.41 Complete Example: Calculator</h2>

<pre><code>try:
   first = float(input("Enter first number: "))
   second = float(input("Enter second number: "))
   operator = input("Enter operator (+, -, *, /): ")

   if operator == "+":
       result = first + second

   elif operator == "-":
       result = first - second

   elif operator == "*":
       result = first * second

   elif operator == "/":
       result = first / second

   else:
       raise ValueError("Invalid operator.")

except ValueError as error:
   print("Error:", error)

except ZeroDivisionError:
   print("Cannot divide by zero.")

else:
   print("Result:", result)

finally:
   print("Calculator operation completed.")</code></pre>


<h2>15.42 Complete Example: Student Marks</h2>

<pre><code>def get_marks():
   try:
       marks = float(input("Enter marks: "))

       if marks < 0 or marks > 100:
           raise ValueError("Marks must be between 0 and 100.")

       return marks

   except ValueError as error:
       print("Error:", error)
       return None


marks = get_marks()

if marks is not None:
   print("Valid marks:", marks)</code></pre>


<h2>15.43 Complete Example: Safe Division</h2>

<pre><code>def safe_divide(a, b):
   try:
       result = a / b

   except ZeroDivisionError:
       return "Cannot divide by zero."

   else:
       return result

   finally:
       print("Division operation completed.")


print(safe_divide(20, 5))
print(safe_divide(20, 0))</code></pre>


<h2>15.44 Best Practices for Exception Handling</h2>

<ul>
   <li>Catch specific exceptions whenever possible.</li>
   <li>Do not use a broad <code>except</code> unnecessarily.</li>
   <li>Provide useful error messages.</li>
   <li>Use <code>finally</code> for cleanup operations.</li>
   <li>Validate user input before processing it.</li>
   <li>Use custom exceptions when they improve program clarity.</li>
   <li>Do not hide important programming errors.</li>
   <li>Keep exception-handling code simple and readable.</li>
</ul>


<h2>15.45 Chapter Summary</h2>

<p>
In this chapter, you learned how Python handles runtime problems using
exception handling. You learned how to detect exceptions, respond to
different errors, validate input, and create custom exceptions.
</p>

<ul>
   <li>What an exception is</li>
   <li>Why exception handling is useful</li>
   <li>The <code>try</code> block</li>
   <li>The <code>except</code> block</li>
   <li>Multiple exception handlers</li>
   <li>The <code>else</code> block</li>
   <li>The <code>finally</code> block</li>
   <li>The <code>raise</code> statement</li>
   <li>Custom exceptions</li>
   <li>Assertions</li>
   <li>Common Python exceptions</li>
   <li>Exception handling with files and functions</li>
</ul>


<h2>15.46 Quick Revision Questions</h2>

<ol>
   <li>What is an exception in Python?</li>
   <li>Why is exception handling important?</li>
   <li>What is the purpose of the <code>try</code> block?</li>
   <li>What is the purpose of the <code>except</code> block?</li>
   <li>When does the <code>else</code> block execute?</li>
   <li>When does the <code>finally</code> block execute?</li>
   <li>What is a <code>ValueError</code>?</li>
   <li>What is a <code>ZeroDivisionError</code>?</li>
   <li>What does the <code>raise</code> statement do?</li>
   <li>What is a custom exception?</li>
</ol>


<h2>15.47 Practice Exercises</h2>

<ol>
   <li>Write a program that safely converts user input into an integer.</li>
   <li>Create a program that handles division by zero.</li>
   <li>Write a program that handles invalid list indexes.</li>
   <li>Write a program that handles missing dictionary keys.</li>
   <li>Create a program that safely opens a file.</li>
   <li>Use <code>try</code>, <code>except</code>, and <code>else</code>
       in a calculator.</li>
   <li>Use <code>finally</code> to display a completion message.</li>
   <li>Create a custom exception for invalid marks.</li>
   <li>Create a custom exception for an invalid age.</li>
   <li>Build a safe student marks input program.</li>
</ol>


<h2>15.48 Mini Project: Safe Student Result Program</h2>

<p>
Create a program that accepts a student's name and marks, validates
the marks, and displays the result without allowing invalid input to
crash the program.
</p>

<pre><code>name = input("Enter student name: ")

try:
   marks = float(input("Enter marks: "))

   if marks < 0 or marks > 100:
       raise ValueError("Marks must be between 0 and 100.")

except ValueError as error:
   print("Error:", error)

else:
   print("Student:", name)
   print("Marks:", marks)

finally:
   print("Result processing completed.")</code></pre>


<h2>15.49 What's Next?</h2>

<p>
<strong>
Next Chapter: Python Object-Oriented Programming – Classes and Objects
</strong>
</p>