Errors are an unavoidable part of programming. No matter how carefully a program is written, unexpected situations can occur at run time: the user may type a letter where a number is expected, a file may not exist, or the network may be down. In Python, these run-time anomalies are called exceptions. An exception is an event that disrupts the normal flow of the program's instructions. When an exception occurs, the Python interpreter stops executing the current code and reports the error by generating a traceback message.
Traditionally, programmers handled such situations by writing lots of if statements to check every possible error condition, which made code long, cluttered and hard to read. Exception handling provides a cleaner and more structured approach. Using the try and except blocks, a program can watch over a suspicious section of code and, when an error occurs, transfer control to a dedicated handler instead of crashing. This chapter explains what exceptions are, how they arise, how to catch them with try-except, how to raise them deliberately with raise, and how to define custom exceptions.
The chapter is important because robust, professional software never crashes silently. A well-designed program anticipates errors, handles them gracefully, and either recovers or informs the user with a meaningful message. In the board examination, exception handling questions commonly test the syntax of try-except-else-finally, the order of except blocks, the difference between raise and raise from, and the trace of a given program containing an intentional error.
An exception is a run-time error that occurs when something unexpected happens during program execution. Unlike a syntax error, which is detected by the interpreter before the program starts running, an exception occurs while the program is actually being executed. Examples include dividing a number by zero, trying to convert the string "abc" to an integer, accessing a list index that does not exist, or attempting to open a file that is not present.
Python has many built-in exception classes. Some of the most common ones are listed below.
x = 10 / 0 # ZeroDivisionError
value = int("hello") # ValueError
lst = [1, 2, 3]
print(lst[5]) # IndexError
d = {"a": 1}
print(d["b"]) # KeyError
num = 5 + "10" # TypeError
When any of these statements runs, Python raises the corresponding exception and terminates the program unless the exception is handled. The line x = 10 / 0 produces ZeroDivisionError: division by zero.
The table below summarizes frequently examined exceptions, but a quick list helps here: ZeroDivisionError (division or modulo by zero), ValueError (inappropriate argument value), TypeError (operation applied to an object of inappropriate type), IndexError (sequence index out of range), KeyError (key not found in dictionary), FileNotFoundError (file does not exist), NameError (name not defined), SyntaxError (invalid Python syntax) and OverflowError (numeric result too large to be represented).
print(undefined_variable) # NameError: name 'undefined_variable' is not defined
The try and except keywords allow a program to handle exceptions gracefully. The code that may cause an exception is placed inside the try block. If an exception occurs, the interpreter immediately jumps to the matching except block and executes its handler. If no exception occurs, the except block is skipped entirely.
try:
num = int(input("Enter a number: "))
print(100 / num)
except ZeroDivisionError:
print("Cannot divide by zero")
except ValueError:
print("Please enter a valid integer")
In the above program, entering 0 triggers ZeroDivisionError and entering hello triggers ValueError. The single try block can have multiple except blocks, one for each type of exception. The interpreter checks the except blocks in order and executes the first one whose exception type matches.
If the exception type is not specified, the except block catches all exceptions. This is convenient but dangerous because it hides unexpected errors and makes debugging difficult.
try:
result = int("abc")
except:
print("Something went wrong")
The keyword as binds the exception to a variable so the program can access its details, such as the message stored in the exception object.
try:
x = 10 / 0
except ZeroDivisionError as e:
print("Error message:", e)
A try statement can have optional else and finally clauses. The else block runs only when no exception occurs in the try block, and it must follow all except blocks. The finally block runs unconditionally, whether or not an exception occurred. It is typically used for cleanup actions such as closing a file or releasing a resource.
try:
num = int(input("Enter a number: "))
result = 100 / num
except ZeroDivisionError:
print("Division by zero not allowed")
except ValueError:
print("Invalid input")
else:
print("Result:", result)
finally:
print("Execution of try block completed")
The finally block executes even if a break, continue or return statement is encountered inside the try block, and even if an exception propagates out of the try. This makes it the most reliable place for cleanup code.
Exceptions can be raised deliberately using the raise statement. This is useful for validating input or forcing an error under a specific condition. A built-in exception class and an optional message can be supplied.
marks = int(input("Enter marks: "))
if marks < 0 or marks > 100:
raise ValueError("Marks must be between 0 and 100")
print("Valid marks:", marks)
The raise statement can also be used inside an except block to re-raise the same exception after logging it, so the caller can handle it. The form raise ValueError("message") from err creates a chained exception that shows the cause.
Programmers can define their own exception classes by inheriting from the built-in Exception class. Custom exceptions make code more readable and allow distinct handling of application-specific errors. The class name typically ends with "Error".
class MarksOutOfRangeError(Exception):
pass
marks = 150
try:
if marks < 0 or marks > 100:
raise MarksOutOfRangeError("Marks out of range")
except MarksOutOfRangeError as e:
print("Custom error caught:", e)
All built-in exceptions are derived from a common base class, BaseException. The class Exception inherits from BaseException and is the base class of nearly all the exceptions programmers should handle. The hierarchy Exception -> ArithmeticError -> ZeroDivisionError shows, for example, that a ZeroDivisionError is also an ArithmeticError. Because of inheritance, an except block written for a parent class also catches its child exceptions, so a broad except Exception: catches almost every practical error. This is why specific exception types should be listed before general ones.
try:
x = 10 / 0
except ArithmeticError:
print("An arithmetic error occurred")
Here the parent class ArithmeticError catches the child ZeroDivisionError.
| Exception | Cause | Example |
|---|---|---|
| ZeroDivisionError | Division or modulo by zero | 10 / 0 |
| ValueError | Inappropriate argument value | int("abc") |
| TypeError | Operation on wrong type | "5" + 5 |
| IndexError | Sequence index out of range | [1,2,3][9] |
| KeyError | Key not present in dictionary | {"a":1}["b"] |
| FileNotFoundError | File does not exist | open("nofile.txt") |
| NameError | Name is not defined | print(x) when x absent |
| Clause | When it Executes |
|---|---|
| try | Code monitored for exceptions |
| except | When matching exception occurs |
| else | Only when no exception occurs in try |
| finally | Always, with or without exception |
except: catches every exception including the system KeyboardInterrupt, hiding real bugs and making debugging difficult.except Exception before except ZeroDivisionError makes the specific handler unreachable.raise with raise from, or miswrite the as clause.else causes them to be skipped when an exception occurs.finally, otherwise it may never run when an exception interrupts the try.raise "error" fails; only exception classes or instances can be raised.except block that prints nothing makes errors invisible and impossible to trace.Exception (or a subclass) to behave like a proper exception.try-except snippet and writing down exactly what is printed.raise ExceptionClassName("message") and remember from err chains the original cause.class MyError(Exception): pass appear often in 3-mark questions.as e to print the exception message in code-writing questions; it shows careful handling.Exception handling transforms a fragile program that crashes on bad input into a robust program that responds intelligently to errors. The try-except structure catches exceptions, else distinguishes the success path, and finally guarantees cleanup. The raise statement lets programs generate their own errors, and custom exception classes give applications a readable, domain-specific error vocabulary. Understanding the built-in exception hierarchy helps a programmer write handlers at the right level of generality. Together these tools form the safety net of professional Python programming. In the next chapter, file handling builds on this foundation by teaching how to read and write persistent data, where exception safety becomes especially important because file operations often fail for reasons outside the programmer's control.