Follow Us
Select Medium / माध्यम चुनें:
Eng (English) Hindi (हिन्दी)
UPMSP • Class XII • Computer Science • Ch 1
Estimated Time: 45 Mins
Study Progress: In Progress

Exception Handling in Python

Exception Handling in Python forms the cornerstone of Unit 1 (Computational Thinking and Programming - 2) in the Uttar Pradesh Madhyamik Shiksha Parishad (UPMSP) Class 12 Computer Science syllabus. While basic programming teaches students how to express sequential, conditional, and repetitive logic under expected operational parameters, real-world computing environments operate amidst unpredictable user actions, volatile hardware states, and external communication interruptions. A program that halts abruptly with an unhandled interpreter crash compromises data integrity, terminates active database transactions without committing or rolling back, and leaves shared operating system resources locked. Mastering structured exception handling equips students with the exact engineering patterns required to build industrial-grade, fault-tolerant Python applications. The Conceptual Spectrum of Software Defects: Computer scientists categorize software flaws into three distinct categories based on when they manifest and how they disrupt execution: (a) Syntax Errors (Parsing Errors): These errors represent direct violations of Python formal grammatical rules, such as omitting a colon at the end of a conditional header, providing mismatched parentheses, or introducing inconsistent indentation levels. Syntax errors are identified by the Python tokenizer and parser during the preliminary translation phase prior to runtime. When a syntax error exists, the Python virtual machine refuses to emit bytecode, and program execution never begins. (b) Logical Errors (Semantic Bugs): These flaws occur when a program executes completely without interpreter crashes or syntax violations, but produces mathematically or procedurally incorrect outputs due to algorithmic misunderstandings. Examples include utilizing an addition operator instead of a multiplication operator in a compound interest calculation or specifying incorrect index boundaries in an array loop. Logical errors cannot be detected automatically by the interpreter; they must be identified through rigorous unit testing, trace tables, and algorithmic inspection. (c) Runtime Exceptions: An exception is an anomalous operational event that occurs during active program execution when a syntactically valid statement attempts an illegal or impossible operation. Examples include dividing a non-zero numerator by zero, attempting to access an index beyond the bounds of a list, requesting a key that does not exist in a dictionary, opening a non-existent file on disk, or attempting to cast an alphabetic string into an integer. Unlike syntax errors, runtime exceptions allow execution to begin normally, but will terminate execution abruptly with a diagnostic traceback if no defensive handling mechanisms are implemented. The Python Built-in Exception Hierarchy: Python organizes all built-in exceptions into a strict object-oriented inheritance tree rooted at the BaseException superclass. Understanding this hierarchical relationship is essential for mastering exception handling because catching a parent class automatically catches all of its derived subclasses. Under BaseException, Python establishes four primary branches: (a) SystemExit: Raised by the sys.exit() function to terminate the interpreter cleanly. (b) KeyboardInterrupt: Raised when the human operator transmits an interrupt signal, typically by pressing Control-C in the terminal. (c) GeneratorExit: Raised when a generator or coroutine is closed. (d) Exception: The foundational superclass for all standard non-fatal, application-level runtime anomalies. All built-in exceptions that programmers should catch and handle derive directly or indirectly from Exception. Within the Exception branch, specialized subclass families categorize operational failures: ArithmeticError: The umbrella parent for numerical computational failures, including ZeroDivisionError (division or modulo by zero), OverflowError (numerical calculations exceeding floating-point representation limits), and FloatingPointError (hardware floating-point operation failures). LookupError: The parent class for sequence and mapping retrieval failures, encompassing IndexError (attempting to retrieve an element at an invalid sequence index) and KeyError (requesting an absent key from a mapping dictionary). ValueError: Raised when a built-in operation or function receives an argument that possesses the correct data type but an inappropriate value, such as int('twenty'). TypeError: Raised when an operation or function is applied to an object of an inappropriate type, such as attempting to add a string to an integer without explicit type conversion. NameError: Raised when a local or global identifier name is not recognized or has not been defined prior to invocation. OSError: The broad system-level error umbrella covering FileNotFoundError (attempting to open or read a non-existent file path), PermissionError (insufficient file system access privileges), and TimeoutError. The Four-Pillar Handling Architecture (try, except, else, finally): Python provides a structured, elegant four-clause architecture to govern the complete lifecycle of guarded execution: (a) The try Block: Programmers encapsulate potentially fragile or volatile statements within the try suite. If every statement within the try block executes successfully without generating an anomaly, the entire except suite is bypassed completely. (b) The except Block: Acts as an active defensive catcher. When an exception occurs within the try suite, normal sequential execution halts immediately on the failing statement. The interpreter searches the sequence of except handlers in top-to-bottom order to find a handler matching the type of the raised exception object. If a match is discovered, the matching except block executes, neutralizing the crash and allowing the program to continue execution beyond the handling structure. (c) The else Block: An optional suite placed immediately after all except clauses. The code within the else block executes if and only if the guarded try block ran to completion without raising any exceptions whatsoever. Placing non-risky secondary operations within the else block keeps the try block minimal, preventing unintended exceptions from being accidentally masked. (d) The finally Block: The ultimate cleanup clause positioned at the conclusion of the structure. The code statements inside the finally block are guaranteed to execute under every conceivable operational circumstance. Whether the try block succeeds cleanly, an exception is caught and handled, an unhandled exception triggers a crash, or an internal statement executes a return, break, or continue instruction, the finally block will always execute before control leaves the construct. This guarantees the reliable deallocation of external system resources such as open file handles, database cursors, and network sockets. Granular and Multiple Exception Handling: Robust programs rarely rely on generic handlers. Instead, they implement granular exception interception using multiple specialized except clauses or grouped tuples. Catching specific exception types allows the application to respond with tailored recovery actions. In multi-except architectures, developers must adhere strictly to subclass ordering principles: specific derived subclasses must always be placed above general parent classes. Placing a parent class (such as Exception or ArithmeticError) above a child class (such as ZeroDivisionError) renders the child handler unreachable dead code, because the parent clause will eagerly intercept the exception object first. Explicit Exception Generation and Re-raising via raise: Beyond reacting to automatic interpreter errors, developers frequently encounter situations where program logic violates high-level business rules even though the low-level syntax remains valid. Python provides the raise statement to explicitly throw an exception object. A programmer can instantiate any built-in exception class with a descriptive contextual message (for example, raise ValueError('Age cannot be a negative value')). Furthermore, executing a bare raise statement inside an active except block re-propagates the currently caught exception up the call stack, enabling an application to log the error locally while allowing higher-level subsystems to perform systemic recovery. Engineering User-Defined Custom Exception Classes: In complex software architectures and UPMSP board examinations, standard built-in exceptions cannot always capture domain-specific semantic boundaries. Python enables developers to design custom user-defined exception classes by inheriting from the built-in Exception superclass. By subclassing Exception, defining customized constructors, and establishing descriptive string representations, developers create domain models that report business rule violations cleanly (for example, InsufficientBalanceError in banking applications or InvalidMarksRangeError in grading systems). Defensive Verification Using the assert Statement: Defensive programming incorporates self-testing sanity checks to verify that internal conditions and algorithm invariants hold true during program execution. The Python assert statement tests a boolean expression; if the evaluated condition yields True, execution continues uninterrupted. If the condition evaluates to False, the interpreter immediately raises an AssertionError accompanied by an optional diagnostic message. In educational and professional engineering, assertions serve as internal contracts for developer debugging rather than as substitutes for user input validation, because assertions can be globally disabled at runtime using Python optimization flags (-O). UPMSP Class 12 Examination Blueprint: For the Uttar Pradesh Board Class 12 Computer Science examination, exception handling forms a vital component of practical and theoretical scoring. Questions frequently test a student ability to trace complex code outputs involving finally blocks with return statements, identify and correct inverted exception hierarchies, construct robust custom exception classes with proper inheritance, and rewrite fragile file processing routines using complete try-except-finally blocks.

Have You Ever Wondered?

On June 4, 1996, the European Space Agency launched the maiden flight of the Ariane 5 rocket from Kourou, French Guiana, carrying a payload valued at more than five hundred million United States dollars. Just thirty-seven seconds after liftoff, while soaring through the stratosphere at supersonic speed, the primary computer system suffered a sudden internal software malfunction. The rocket abruptly swung ninety degrees in the wrong direction, encountered extreme aerodynamic pressure, and tore itself apart before safety controllers triggered emergency self-destruction. The catastrophic failure was not triggered by physical combustion or engine malfunction. It was caused by a single unhandled numerical exception: an internal software routine attempted to convert a sixty-four bit floating-point velocity measurement into a sixteen-bit signed integer. Because the calculated velocity value exceeded 32,767, the processor triggered an unhandled arithmetic overflow exception. With no defensive exception handling routines to catch the event, the navigation computer shut down instantly, leaving the rocket completely uncontrolled. In professional software engineering, exceptions are not rare anomalies; they are inevitable physical realities. Memory buffers exhaust, hard drives disconnect, network sockets drop, and users enter unexpected input formats. Writing fragile code that executes only under ideal conditions is unacceptable in commercial environments. Exception handling is the fundamental discipline of constructing resilient, fault-tolerant software systems that detect disruptive execution anomalies gracefully, deallocate critical system resources reliably, and maintain continuous operation without abrupt termination.

Why This Chapter Matters

In modern computing, software applications do not run inside sterile, predictable laboratories; they operate across decentralized cloud environments, interact with unpredictable human end-users, and communicate across volatile global networks. A single unhandled exception in an automated air traffic control system, a high-frequency trading platform, or an intensive care medical monitor can lead to disastrous financial loss or fatal human consequences. In commercial production systems, resilience is as crucial as functional correctness. Professional software engineers spend substantial portions of their development lifecycle anticipating failure modes, establishing defensive boundaries, and guaranteeing that system state remains coherent when unexpected anomalies strike. Mastering exception handling elevates a student from an introductory script writer into a disciplined software engineer capable of delivering robust, fault-tolerant applications that protect critical data assets, release scarce hardware resources reliably, and maintain uninterrupted uptime under challenging real-world conditions.

Before You Begin (Prerequisites)

  • Proficiency with Python core syntax, data types, variable scopes, and arithmetic/logical operators.
  • Comprehensive understanding of procedural control flow including if-elif-else branching and for/while iteration.
  • Familiarity with standard built-in data collections including lists, tuples, dictionaries, and strings.
  • Basic knowledge of functional decomposition, parameter passing, return statements, and the runtime call stack.
  • Introductory understanding of object-oriented programming concepts including classes, inheritance, and instances.

What You Will Learn (Core Objectives)

  • Differentiate precisely between compile-time syntax errors, algorithmic logical flaws, and runtime exceptions.
  • Trace and diagram the Python built-in exception hierarchy from BaseException down to specialized operational subclasses.
  • Construct robust guarded execution blocks utilizing structured try, except, else, and finally clauses.
  • Implement multi-tier exception handlers using tuples and specialized clauses while avoiding unreachable dead code traps.
  • Analyze the unconditional execution guarantee of the finally block across return statements and unhandled crashes.
  • Capture and inspect exception instance attributes using the 'as' alias syntax to extract diagnostic error messages.
  • Trigger built-in exceptions deliberately using the raise statement to enforce application constraints and boundary rules.
  • Re-propagate active exceptions across the call stack using the bare raise statement following local audit logging.
  • Engineer user-defined custom exception classes inheriting from Exception with specialized constructors and messages.
  • Apply defensive assertions using the assert statement to validate internal algorithm invariants and contract preconditions.

Chapter Roadmap & Progression

1 Module 1: The Spectrum of Software...
2 Module 2: The Python Exception Clas...
3 Module 3: Structured Handling with...
4 Module 4: The Complete Lifecycle: t...
5 Module 5: Explicitly Raising Except...
6 Module 6: Engineering User-Defined...
7 Module 7: Defensive Programming wit...

Complete Concept Guide (100% Curriculum Coverage)

Module 1: The Spectrum of Software Defects & Anatomy of an Exception

UPMSP CLASS 12 COMPUTER SCIENCE (SUBJECT CODE: 403) • UNIT 1 EXCEPTION HANDLING IN PYTHON Defects Taxonomy • Inheritance Tree • Lifecycle State Machine • Custom Exceptions • Assertions UPMSP CH-01 PANEL 1: THE TAXONOMY OF SOFTWARE DEFECTS (SYNTAX VS RUNTIME VS LOGICAL) 1. SYNTAX (PARSING) ERRORS Detection Phase: Compilation / Parsing Execution Starts? NO (Zero bytecode emitted) Root Cause: Violating Python grammatical rules (missing colons, improper indentation, unbalanced parentheses/quotes). if x > 10 # SyntaxError: ':' print("Hello" def calc(a b): 2. RUNTIME EXCEPTIONS Detection Phase: During Active Execution Execution Starts? YES (Halts at anomaly) Root Cause: Syntactically valid statement triggers an illegal operation (div by 0, missing file, index out of bounds, wrong type cast). ans = 10 / 0 # ZeroDivisionError num = int("abc") # ValueError arr[99] # IndexError 3. LOGICAL (SEMANTIC) BUGS Detection Phase: Testing / User Audit Execution Starts? YES (Runs completely) Root Cause: Flawed algorithm or formula logic (using + instead of *, incorrect loop bounds, wrong operator precedence). NO crash! avg = a + b / 2 # Wrong: (a+b)/2 # Program executes smoothly, # but produces incorrect output! PANEL 2: THE PYTHON BUILT-IN EXCEPTION CLASS HIERARCHY (INHERITANCE TREE) BaseException SystemExit KeyboardInterrupt Exception (Standard) GeneratorExit ArithmeticError • ZeroDivisionError • OverflowError • FloatingPointError LookupError • IndexError • KeyError ValueError & TypeError • ValueError: int("x") • TypeError: 5 + "str" OSError (Environment) • FileNotFoundError • PermissionError • IOError / Timeout User-Defined • class MyError(Exception) • InvalidAgeError • BalanceLowError GOLDEN RULE OF SUBCLASS ORDER: Catch specific child exceptions FIRST (ZeroDivisionError) BEFORE parent exceptions (ArithmeticError / Exception)! PANEL 3: TRY-EXCEPT-ELSE-FINALLY LIFECYCLE & FINITE STATE MACHINE FLOW 1. try: BLOCK Contains risky / guarded statements Exception Raised? YES (Exception) 2A. except Handler: BLOCK Matches exception type. Else block is SKIPPED entirely! NO (Zero Errors) 2B. else: BLOCK Executes ONLY when try block finishes with NO exceptions! 3. finally: BLOCK (MANDATORY EXECUTION) GUARANTEED execution under ALL circumstances (Runs even after return statements or unhandled crashes!) PRACTICAL MANDATE: Always close open files (file.close()), network sockets, and database connections inside finally block! PANEL 4: EXPLICIT RAISING & USER-DEFINED CUSTOM EXCEPTION ENGINEERING A. THE raise KEYWORD (FORCED EXCEPTIONS) 1. Triggering Built-in Exceptions with Custom Notes: if age < 18: raise ValueError("Applicant must be at least 18 years old") 2. Re-raising Caught Exceptions for Upper-Level Handlers: except DatabaseConnectionError as err: log_to_file("DB Offline: " + str(err)) raise # Re-raises the active exception unchanged! Crucial: A bare 'raise' inside an except block re-propagates the active exception. B. USER-DEFINED EXCEPTION CLASS PATTERN 1. Inherit from 'Exception' Base Superclass: class InvalidMarksError(Exception): def init(self, marks, msg="Marks out of range [0-100]"): self.marks = marks self.msg = f"{msg}: received {marks}" super().init(self.msg) # Usage in UPMSP board examination programs: if score < 0 or score > 100: raise InvalidMarksError(score) UPMSP Standard: Custom exceptions communicate business domain violations cleanly. PANEL 5: THE ASSERTION MATRIX, EXAMINER TRAPS & BOARD BEST PRACTICES A. THE assert STATEMENT Defensive debugging condition check: assert condition, message # If condition is False, raises: # AssertionError: message Exam Note: Used for internal sanity invariants and debugging, NOT for validating untrusted user inputs (use try-except!). Can be disabled via Python -O flag. B. 3 FATAL EXAMINER TRAPS TRAP 1: Bare except: Clause Catching all exceptions indiscriminately masks Ctrl+C (KeyboardInterrupt) & typos! TRAP 2: Inverted Handler Hierarchy Placing except Exception BEFORE except ZeroDivisionError makes child dead code. TRAP 3: Silent Suppression with pass except Exception: pass Silently swallows bugs, making errors impossible to diagnose and troubleshoot! C. UPMSP SCORING BLUEPRINT 1. Precision in Trace Output Questions Always evaluate 'finally' lines even when try returns or raises an unhandled error. 2. Minimal Scope for try Blocks Keep try blocks concise. Only wrap the exact line that might fail, not the whole script. 3. Custom Exceptions in Class 12 Must subclass Exception and call super(). Always provide informative error messages to earn full 5/5 marks in board exams.

Pedagogical Anchor & Curriculum Alignment

In accordance with the Uttar Pradesh Madhyamik Shiksha Parishad (UPMSP) Class 12 Computer Science syllabus, this module deconstructs the foundational boundaries between compile-time syntax flaws, semantic logical bugs, and disruptive runtime exceptions. Students must master how the Python interpreter processes source code, why unhandled exceptions cause abrupt program termination, and how professional software engineers construct fault-tolerant systems.

1. The Tripartite Taxonomy of Software Defects

In computer science, software defects are never monolithic. A program may fail to run, run and crash midway, or run to completion while generating completely erroneous calculations. To debug effectively and engineer resilient software, developers must classify errors into three distinct categories based on their detection phase, interpreter state, and operational consequence:

1. Syntax Errors (Parsing Phase)

Violations of the formal grammatical grammar of the Python language. Detected by the parser before execution starts. The interpreter refuses to generate bytecode; zero statements are executed.

  • Missing colons after if, for, def
  • Unbalanced parentheses or brackets
  • Invalid variable assignment (e.g., 5 = x)
  • Irregular or mismatched indentation
2. Logical Errors (Semantic Flaws)

Flaws in the programmer's algorithmic thinking or mathematical formulas. The program is syntactically valid and runs completely to the end without crashing, but produces wrong answers.

  • Using + instead of * in formula
  • Incorrect operator precedence: a + b / 2
  • Off-by-one boundary errors in loops
  • Inverted conditional test: < vs >
3. Runtime Exceptions (Execution Phase)

Anomalous, disruptive events that occur during active program execution when a syntactically valid statement attempts an illegal operation. The program starts normally, executes statements up to the fault, and crashes abruptly.

  • Division by zero: 100 / 0
  • Missing file on disk: open('data.csv')
  • Type conversion: int('ninety')
  • Out-of-range sequence access: list[99]

Comprehensive Diagnostic Comparison Matrix

Attribute Syntax (Parsing) Error Logical (Semantic) Error Runtime Exception
Detection Mechanism Python Tokenizer and AST Parser Human Code Review, Unit Tests, Assertion Audits Python Virtual Machine (PVM) Runtime Engine
Detection Timing Pre-execution Compilation Phase Post-execution Verification Phase Active Execution Phase (At failing line)
Bytecode Generation None (Aborted immediately) Full Bytecode Generated (.pyc created) Full Bytecode Generated (.pyc created)
Program Execution Status Zero lines execute; halted at line 0 100% of statements execute to completion Executes normally until the disruptive line
Can It Be Caught with try-except? NO (Syntax checked before try runs) NO (No exception object is raised) YES (Primary target of try-except)
Diagnostic Symptom SyntaxError: invalid syntax Mathematically incorrect output Python Traceback and program crash

2. Anatomy of an Unhandled Exception & The Traceback Mechanism

When a runtime exception occurs inside a Python script and is not enclosed within a defensive try-except construct, the Python interpreter takes three automatic actions:

  1. Halts active execution immediately: The current thread stops processing subsequent instructions. Any open transactions, unsaved buffers, or locks are left in an incomplete state.
  2. Instantiates an Exception Object: The interpreter instantiates a concrete object belonging to a subclass of Exception (e.g., ZeroDivisionError, ValueError), encapsulating error metadata, the memory address, and a human-readable diagnostic message.
  3. Prints a Diagnostic Traceback to stderr: The interpreter walks backward through the call stack from the current failing frame up to the global module scope, outputting the exact sequence of function invocations that led to the crash.
Dissecting a Python Traceback Python 3.12+ Standard Formatter
Traceback (most recent call last):
  File "grade_calculator.py", line 18, in <module>
    compute_average_score()
  File "grade_calculator.py", line 12, in compute_average_score
    calculate_class_mean(student_scores)
  File "grade_calculator.py", line 6, in calculate_class_mean
    return sum(marks) / len(marks)
           ~~~~~~~~~~~^~~~~~~~~~~~
ZeroDivisionError: division by zero

Traceback Dissection Breakdown for Board Exams:

  • Header: Traceback (most recent call last): indicates that the error is printed chronologically, with the initial caller at the top and the actual failing statement at the bottom.
  • Stack Frames: Each frame identifies the file path, line number, and enclosing function or module name (e.g., line 18 in <module> called line 12 in compute_average_score, which called line 6 in calculate_class_mean).
  • Caret Markers (Python 3.11+): The squiggly markers (~~~~~~~~~~~^~~~~~~~~~~~) pinpoint the exact sub-expression that triggered the failure (dividing by len(marks) which evaluated to 0).
  • Exception Line: The final line displays the exact Exception Class Name (ZeroDivisionError) followed by the Diagnostic Description (division by zero).
The Engineering Imperative for Exception Handling

In production software, crashing is unacceptable. A web server handling 50,000 concurrent students checking their UPMSP board exam results cannot shut down because one student submitted an empty form. Exception handling enables Graceful Degradation: capturing the anomaly, logging diagnostic details for administrators, informing the user with an actionable message, and maintaining continuous system uptime.

Module 2: The Python Exception Class Hierarchy & Built-in Taxonomy

Module Objectives & Core Concepts

In Python, all exceptions are classes structured in an object-oriented inheritance tree. In this module, students will examine the architectural hierarchy from BaseException down to concrete subclasses, understand why polymorphic inheritance governs handler behavior, and master the high-stakes Golden Rule of Subclass Ordering tested in UP Board examinations.

1. The Architectural Exception Tree

Python organizes built-in exceptions into a strict single-inheritance tree. The absolute root of all exception classes is BaseException. Understanding this hierarchy is vital because catching a parent class in an except clause will automatically catch all classes that inherit from it (polymorphic capture).

Python Built-in Exception Tree UPMSP Prescribed Standard
BaseException
 ├── SystemExit (Raised by sys.exit(); non-fatal interpreter termination)
 ├── KeyboardInterrupt (Raised by user pressing Ctrl+C; interactive break)
 ├── GeneratorExit (Raised when a generator or coroutine closes)
 └── Exception (The root of ALL standard, application-level exceptions)
      ├── ArithmeticError (Base class for arithmetic computation failures)
      │    ├── ZeroDivisionError (Division or modulo by zero)
      │    ├── OverflowError (Numeric result too large to represent)
      │    └── FloatingPointError (Floating-point calculation failure)
      ├── LookupError (Base class for key or index lookup failures)
      │    ├── IndexError (Sequence subscript out of range)
      │    └── KeyError (Mapping key not found in dictionary)
      ├── ValueError (Correct data type, but illegal/inappropriate value)
      │    └── UnicodeError (Unicode encoding/decoding failures)
      ├── TypeError (Operation applied to an incompatible object type)
      ├── NameError (Local or global identifier name not found)
      │    └── UnboundLocalError (Local variable referenced before assignment)
      ├── AttributeError (Attribute reference or assignment fails)
      ├── ImportError (Import statement fails to find/load a module)
      │    └── ModuleNotFoundError (Subclass raised when module does not exist)
      ├── OSError (System-related errors emitted by operating system calls)
      │    ├── FileNotFoundError (Requested file or directory does not exist)
      │    ├── PermissionError (Insufficient OS permissions for operation)
      │    ├── FileExistsError (Attempting to create file/dir that already exists)
      │    └── IsADirectoryError (File operation requested on a directory)
      ├── EOFError (Input function hits end-of-file condition without data)
      ├── MemoryError (Operation runs out of heap memory)
      ├── RecursionError (Interpreter maximum recursion depth exceeded)
      └── User-Defined Exceptions (Custom classes created by programmers)

2. Detailed Taxonomy of Core UPMSP Built-in Exceptions

ZeroDivisionError

Parent: ArithmeticError → Exception

Raised when the second argument of a division (/, //) or modulo (%) operation evaluates to zero.

x = 25 / 0 # ZeroDivisionError
ValueError

Parent: Exception

Raised when a built-in function receives an argument of the correct data type, but containing an inappropriate value.

val = int("Lucknow") # ValueError
TypeError

Parent: Exception

Raised when an operation is attempted between incompatible types, such as adding a string to an integer without explicit casting.

res = "Roll No: " + 102 # TypeError
IndexError

Parent: LookupError → Exception

Raised when a sequence subscript (list, tuple, string) is out of range of existing elements.

marks = [85, 90]; m = marks[5] # IndexError
KeyError

Parent: LookupError → Exception

Raised when a dictionary key is requested using subscript notation (dict[key]) and the key does not exist.

student = {'name': 'Ravi'}; print(student['age'])
FileNotFoundError

Parent: OSError → Exception

Raised when a file or directory is requested for opening, reading, or deletion, but cannot be located on the file system.

f = open('absent_file.txt', 'r') # FileNotFoundError

3. The Golden Rule of Subclass Ordering

CRITICAL BOARD EXAM RULE: Subclass Hierarchy Order

Because an except ParentClass: clause catches all objects instantiated from ParentClass or any of its derived subclasses, multiple except handlers must ALWAYS be arranged from the most specific derived subclass at the top to the most general parent class at the bottom. Placing a parent class before a child class makes the child clause unreachable dead code.

INCORRECT ORDER (Fatal Trap)
try:
    res = 100 / int(user_input)
except Exception as e:
    # EAGERLY CATCHES EVERYTHING!
    print("General Exception caught:", e)
except ZeroDivisionError:
    # DEAD CODE! NEVER REACHED!
    print("Cannot divide by zero!")
CORRECT ORDER (UPMSP Standard)
try:
    res = 100 / int(user_input)
except ZeroDivisionError:
    # SPECIFIC SUBCLASS FIRST
    print("Cannot divide by zero!")
except ValueError:
    # SPECIFIC SIBLING SUBCLASS
    print("Invalid numeric string!")
except Exception as e:
    # GENERAL PARENT CATCH-ALL AT END
    print("Unexpected error:", e)

Module 3: Structured Handling with try and except Blocks

Guardians of Code: The try-except Construct

The try and except statements form Python's primary defensive perimeter against unexpected execution failures. In this module, students learn the exact syntax, scope isolation best practices, multi-handler dispatch mechanics, tuple grouping, and how to inspect error payloads using the as alias.

1. Guarded Execution Mechanics

A try block acts as an isolated execution sandbox. When Python enters a try block, it monitors every executed statement for runtime exceptions:

  • Happy Path (No Exceptions): If all statements in the try block execute without triggering an exception, the entire except suite is completely ignored and bypassed. Program execution proceeds directly to the subsequent statements or the else/finally blocks.
  • Disruptive Path (Exception Raised): As soon as an exception occurs on a statement within the try block, execution of the try block halts immediately. Subsequent statements inside the try block are skipped. The interpreter jumps directly into the except suite to locate a matching handler.
Execution Trace Demonstration Single Handler Pattern
print("Step 1: Program initializing")
try:
    print("Step 2: Entering guarded try block")
    numerator = 100
    denominator = 0
    quotient = numerator / denominator  # <-- FAILS HERE: ZeroDivisionError
    print("Step 3: This statement NEVER executes!")
except ZeroDivisionError:
    print("Step 4: Exception successfully caught! Handled division by zero.")

print("Step 5: Execution continues smoothly without crashing.")

Console Output of the Above Script:

Step 1: Program initializing
Step 2: Entering guarded try block
Step 4: Exception successfully caught! Handled division by zero.
Step 5: Execution continues smoothly without crashing.

2. Granular Multi-Except Handlers

A single try block can encounter multiple distinct failure modes. For example, opening a file, reading a line, converting it to an integer, and computing a percentage can fail due to a missing file (FileNotFoundError), an invalid numeric format (ValueError), or a zero denominator (ZeroDivisionError). Professional software provides dedicated, granular recovery logic for each specific failure mode:

def process_student_score(file_path):
    try:
        f = open(file_path, "r")
        raw_data = f.readline().strip()
        f.close()
        
        score = int(raw_data)
        percentage = (score / 500) * 100
        return percentage

    except FileNotFoundError:
        print("RECOVERY ACTION: Score file not found. Creating default record.")
        return 0.0

    except ValueError:
        print("RECOVERY ACTION: Data in file is corrupted/non-numeric. Flagging audit.")
        return None

    except ZeroDivisionError:
        print("RECOVERY ACTION: Maximum marks cannot be zero. Check config.")
        return 0.0

3. Grouping Exceptions with Tuples

When multiple distinct exceptions share identical recovery logic, duplicating code across multiple except blocks violates the DRY (Don't Repeat Yourself) principle. Python allows developers to group multiple exception types into a parenthesized tuple:

try:
    num = int(input_data)
    result = dataset[num]
except (ValueError, IndexError) as err:
    # Handles BOTH bad numeric string AND out-of-bounds index identically
    print(f"Lookup parameter error: {err}")

Important Syntax Note: The parentheses around (ValueError, IndexError) are mandatory. Omitting parentheses (e.g., except ValueError, IndexError:) is an obsolete Python 2 syntax that produces a SyntaxError in modern Python 3.

4. Exception Introspection with the 'as' Alias

When an exception is caught, the programmer can capture the instantiated exception object into a variable using the as variable_name clause. This object contains diagnostic information provided by the interpreter or author:

  • str(err): Returns the primary human-readable error message.
  • err.args: A tuple containing the arguments passed to the exception constructor.
  • type(err).name: Returns the exact string name of the exception class (e.g., 'ZeroDivisionError').

Module 4: The Complete Lifecycle: try-except-else-finally Architecture

The Four Pillars of Robust Exception Handling

Python is one of the few mainstream programming languages that provides a dedicated else clause alongside try, except, and finally. In this module, UPMSP Class 12 students will master the complete four-pillar execution lifecycle, understand why else prevents accidental error masking, and analyze the unconditional execution guarantee of finally across function return statements.

1. The Four-Clause Architectural Blueprint

A complete, professional exception handling structure adheres to the following formal syntactic sequence:

try:
    # 1. GUARDED SUITE:
    # Contains fragile operations that might raise an exception.
    # Execution halts immediately on the first statement that fails.
    risky_operation()

except SpecificException as err:
    # 2. DEFENSIVE HANDLER:
    # Executes ONLY if an exception matching SpecificException occurs.
    handle_recovery(err)

else:
    # 3. UN-GUARDED SUCCESS SUITE:
    # Executes ONLY if the try block ran to completion with ZERO exceptions.
    # Code here is NOT guarded by the except clauses above.
    execute_on_success()

finally:
    # 4. MANDATORY CLEANUP SUITE:
    # Executes UNCONDITIONALLY under ALL circumstances (success, failure, return).
    release_resources()

2. The Strategic Role of the 'else' Clause

A common question asked by UPMSP students is: "Why do we need an else clause if we can just write those statements at the end of the try block?"

The answer lies in Accidental Exception Masking. If you place non-risky secondary statements inside the try block, and one of those secondary statements happens to raise an exception of the same type that your except clause is listening for, the handler will catch an error from the wrong source, creating a phantom recovery action that masks a real bug.

POOR PATTERN (Bloated try Block)
try:
    # Step 1: Risky file open
    f = open('scores.txt', 'r')
    data = f.read()
    f.close()
    
    # Step 2: Unrelated business logic
    # If calculate_stats() raises FileNotFoundError,
    # it gets caught as if scores.txt was missing!
    calculate_stats(data)
except FileNotFoundError:
    print("scores.txt not found!")
CLEAN PATTERN (Using else Clause)
try:
    # Only guard the exact risky operation
    f = open('scores.txt', 'r')
except FileNotFoundError:
    print("scores.txt not found!")
else:
    # Runs only if open() succeeded!
    data = f.read()
    f.close()
    calculate_stats(data)

3. The Unconditional Guarantee of the 'finally' Clause

The finally block is the most rigorously enforced clause in the Python runtime. The statements inside finally are guaranteed to execute under every possible execution trajectory:

  • Scenario 1: Clean Execution: try → else → finally → subsequent code.
  • Scenario 2: Caught Exception: try (fails) → matching except → finally → subsequent code.
  • Scenario 3: Uncaught / Unhandled Exception: try (fails with unhandled error) → finally executes → interpreter crashes and prints Traceback.
  • Scenario 4: Early Function Return: If a return, break, or continue statement is encountered inside try or except, Python pauses the return, executes the finally block completely, and only then delivers the return value back to the caller!
Classic Board Examination Trace Problem UPMSP High-Yield Question
def mystery_function(x, y):
    try:
        print("Point Alpha")
        result = x / y
        print("Point Beta")
        return result
    except ZeroDivisionError:
        print("Point Gamma: Caught division by zero")
        return -1
    else:
        print("Point Delta: Success!")
    finally:
        print("Point Omega: Executing finally cleanup")

# Test Call 1:
print("Return value:", mystery_function(10, 2))
print("------------------------")
# Test Call 2:
print("Return value:", mystery_function(10, 0))

Step-by-Step Execution Analysis & Output:

Call 1: mystery_function(10, 2):

  1. Enters try, prints Point Alpha.
  2. Calculates 10 / 2 = 5.0 without error.
  3. Prints Point Beta.
  4. Hits return 5.0. Notice: Python pauses the return!
  5. Because a return occurred in try, the else block is NOT executed.
  6. The finally block executes: prints Point Omega: Executing finally cleanup.
  7. The paused return completes, delivering 5.0 to the print call.

Call 2: mystery_function(10, 0):

  1. Enters try, prints Point Alpha.
  2. Attempts 10 / 0: raises ZeroDivisionError! Point Beta is skipped.
  3. Enters matching except block: prints Point Gamma: Caught division by zero.
  4. Hits return -1. Python pauses the return!
  5. The finally block executes: prints Point Omega: Executing finally cleanup.
  6. The paused return completes, delivering -1 to the print call.
Point Alpha
Point Beta
Point Omega: Executing finally cleanup
Return value: 5.0
------------------------
Point Alpha
Point Gamma: Caught division by zero
Point Omega: Executing finally cleanup
Return value: -1

Module 5: Explicitly Raising Exceptions & Re-raising Mechanics

Module Focus: Voluntarily Triggering Exceptions

In programming, errors are not always caused by illegal hardware or interpreter operations. Frequently, input data is completely valid syntactically, but violates fundamental business logic or domain boundaries (e.g., negative ages, withdrawal exceeding bank balance). In this module, students master the raise keyword, learn how to re-raise exceptions cleanly, and examine exception chaining.

1. The 'raise' Statement Syntax & Semantics

The raise statement is used to explicitly trigger an exception. A programmer can raise an instance of any class that derives from BaseException (typically from Exception):

# Syntax 1: Raising with an instantiated object and descriptive string
raise ValueError("Marks must be between 0 and 100")

# Syntax 2: Raising by class name (Python instantiates with zero arguments)
raise NotImplementedError

Practical UPMSP Example: Input Range Enforcement

Consider a function for calculating the scholarship grade in a UP Board intermediate school. Even if the user enters a valid floating-point number, values below 0 or above 100 violate school policy:

def register_student_marks(name, marks):
    if not isinstance(marks, (int, float)):
        raise TypeError(f"Marks must be numeric, received {type(marks).name}")
    
    if marks < 0 or marks > 100:
        raise ValueError(f"Invalid marks score: {marks}. Must be in range [0, 100]")
        
    print(f"Successfully recorded: {name} -> {marks} marks")

# Testing the defensive function:
try:
    register_student_marks("Amit Verma", 105)
except ValueError as err:
    print(f"Validation Rejected: {err}")
except TypeError as err:
    print(f"Type Error: {err}")

2. Re-raising Exceptions (The Bare 'raise')

In multi-layered enterprise software, a lower-level module (such as a database connector or file reader) may catch an exception to perform a local task (e.g., writing an audit entry to a security log or rolling back an uncommitted transaction), but should not pretend the problem is resolved. It must notify higher-level callers that the operation failed.

Executing a bare raise keyword with no arguments inside an except block causes Python to re-throw the currently active exception object up the call stack, preserving the original traceback untouched:

def load_configuration_file(filename):
    try:
        f = open(filename, "r")
        config = f.read()
        f.close()
        return config
    except FileNotFoundError as err:
        # Step 1: Perform local logging
        print(f"[SECURITY AUDIT LOG] Missing critical config file: {filename}")
        # Step 2: Re-raise the exact same exception up to the main application
        raise  # Re-propagates FileNotFoundError with original stack trace!
BOARD EXAM TRAP: Bare raise Outside of an except Block

If you write a bare raise statement outside of an active except block where no exception is currently being handled, Python raises a RuntimeError: No active exception to reraise.

Module 6: Engineering User-Defined Custom Exception Classes

Domain-Specific Modeling: Custom Exception Classes

While built-in exceptions like ValueError and IndexError are ideal for language-level operations, complex domain applications (banking, healthcare, student registration) require semantically rich exceptions tailored to business rules. In this module, UPMSP students learn how to design, instantiate, raise, and handle user-defined exception classes.

1. Anatomy of a User-Defined Exception

To create a custom exception class in Python, the class must inherit from the built-in Exception superclass (or one of its derived subclasses like ValueError). Never inherit directly from BaseException, as that would bypass standard application catch blocks.

Pattern 1: Minimal Custom Exception

For simple signaling where only a unique exception type name is needed:

class InsufficientFundsError(Exception):
    # Raised when an account withdrawal exceeds available balance.
    pass

Pattern 2: Fully Engineered Custom Exception with Metadata

In UPMSP Class 12 practical examinations, top-scoring students construct custom exceptions with customized constructors (init), encapsulated attributes, and custom string representations (str):

Full Custom Exception Pattern 5-Mark Board Examination Standard
class InvalidAgeForVotingError(Exception):
    # Custom exception raised when a voter is below statutory age limit
    
    def init(self, age, minimum_age=18, message="Citizen is ineligible to vote"):
        self.age = age
        self.minimum_age = minimum_age
        self.message = f"{message}: Current age {age} is below required {minimum_age} years."
        # Invoke the superclass constructor to set standard exception args
        super().init(self.message)

    def str(self):
        return f"[ELECTION COMMISSION RULE VIOLATION] {self.message}"

2. Complete Board Exam Implementation: Banking System Case Study

The following complete program illustrates the design, raising, and handling of custom exceptions in a realistic banking ledger system:

# Step 1: Define Custom Exception Hierarchy
class BankingError(Exception):
    # Base exception for all banking transaction errors
    pass

class InsufficientBalanceError(BankingError):
    def init(self, balance, withdraw_amount):
        self.balance = balance
        self.withdraw_amount = withdraw_amount
        self.deficit = withdraw_amount - balance
        self.msg = f"Deficit of Rs. {self.deficit}. Available: Rs. {balance}, Requested: Rs. {withdraw_amount}"
        super().init(self.msg)

class MinimumBalanceViolationError(BankingError):
    def init(self, balance_after_withdrawal, minimum_limit=1000):
        self.msg = f"Balance after withdrawal (Rs. {balance_after_withdrawal}) drops below minimum requirement of Rs. {minimum_limit}"
        super().init(self.msg)

# Step 2: Implement Domain Class with Validation
class BankAccount:
    def init(self, account_number, holder_name, initial_balance=5000):
        self.account_number = account_number
        self.holder_name = holder_name
        self.balance = initial_balance

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("Withdrawal amount must be strictly positive.")
            
        if amount > self.balance:
            raise InsufficientBalanceError(self.balance, amount)
            
        projected_balance = self.balance - amount
        if projected_balance < 1000:
            raise MinimumBalanceViolationError(projected_balance)
            
        self.balance = projected_balance
        print(f"Withdrawal Successful! Remaining Balance: Rs. {self.balance}")
        return self.balance

# Step 3: Test Driver with Exception Handling
account = BankAccount("UP-SB-10924", "Sunita Sharma", 4000)

test_withdrawals = [1500, 2000, 10000, -50]

for amount in test_withdrawals:
    print(f"\nInitiating withdrawal of Rs. {amount}...")
    try:
        account.withdraw(amount)
    except InsufficientBalanceError as err:
        print(f"TRANSACTION DECLINED: {err}")
    except MinimumBalanceViolationError as err:
        print(f"MINIMUM BALANCE BREACH: {err}")
    except ValueError as err:
        print(f"INVALID REQUEST: {err}")
    except Exception as err:
        print(f"SYSTEM ANOMALY: {err}")
    finally:
        print(f"Audit Status: Account {account.account_number} ledger verified.")

Module 7: Defensive Programming with assert & Production Best Practices

Defensive Coding: Invariants, Contracts & Anti-Patterns

Defensive programming is the practice of designing software to continue functioning properly despite unexpected inputs or conditions. In this final module, UPMSP students master the assert statement, explore why assertions are not replacements for exception handling, and review the four critical anti-patterns that frequently deduct marks in board examinations.

1. The 'assert' Statement Mechanics

The assert statement tests a boolean expression as an invariant contract. If the expression evaluates to True, the program proceeds without interruption. If the expression evaluates to False, the interpreter immediately raises an AssertionError:

# Formal Syntax:
assert <condition>, <optional_error_message>

# Equivalent Internal Logic:
if not <condition>:
    raise AssertionError(<optional_error_message>)

When to Use Assertions vs. Exception Handling

Feature assert Statement try-except Structure
Primary Purpose Internal sanity checking & debugging during development. Handling predictable real-world runtime anomalies in production.
Target Errors Bugs in developer's code that should never occur if logic is correct. External failures (bad user input, missing files, network drops).
Production Disabling YES (Stripped out when run with python -O) NO (Always active and executed)
Recovery Strategy Crash immediately to alert developer during testing. Gracefully handle, log, recover, and continue operation.

2. The 4 Deadly Anti-Patterns in Exception Handling

ANTI-PATTERN 1: The Bare except: Clause

Writing except: with no exception class catches everything, including KeyboardInterrupt (Ctrl+C) and SystemExit! The user cannot stop an infinite loop in the terminal.

except: # DANGEROUS! Never use bare except.
ANTI-PATTERN 2: Silent Swallowing with 'pass'

Catching an exception and executing only pass completely conceals errors. Corrupted states propagate invisibly throughout the program, making debugging impossible.

except Exception: pass # HORRIBLE PRACTICE!
ANTI-PATTERN 3: Catching Overly Broad Exception

Catching Exception everywhere masks unrelated programming bugs (such as typing mistakes leading to NameError or AttributeError) that should be fixed in code.

except Exception: # Use specific subclasses first!
ANTI-PATTERN 4: Monolithic try Blocks

Wrapping an entire 200-line script inside a single try block destroys granular error diagnosis. Keep try blocks laser-focused on the exact lines that risk failure.

try: # 100 lines of code... # Bad practice

3. Production Clean Code Summary

  • Always catch the most specific exception class possible.
  • Use finally (or context managers with with) to guarantee resource cleanup.
  • Provide meaningful, actionable error messages in raise statements.
  • Inherit from Exception when designing user-defined exception classes.
  • Use assert exclusively for development assertions and internal algorithm invariants, not for input sanitization.

Conceptual Solved Examples & Case Studies

Example 1
Differentiate between a Syntax Error and a Runtime Exception in Python. Illustrate each with a one-line code snippet. (UPMSP Board Question)
Step-by-Step Solution:

Step 1: Conceptual Distinction (1 Mark):

  • Syntax Error: Violates Python's grammatical syntax rules. It is detected by the parser prior to execution during compilation. If a syntax error exists, the interpreter emits no bytecode and zero statements are executed.
  • Runtime Exception: Occurs during active program execution when a syntactically valid statement attempts an illegal operation (such as division by zero or invalid type conversion). Execution begins normally but terminates abruptly when the faulting statement is reached.

Step 2: Illustrative Code Snippets (1 Mark):

# Syntax Error (Missing colon at end of header - Caught before execution):
if x > 10

# Runtime Exception (Syntactically valid, but raises ZeroDivisionError at runtime):
quotient = 100 / 0
Example 2
Analyze the following Python code snippet. Identify the structural flaw in the exception handling architecture, explain the consequence of this flaw, and write the corrected code. (UPMSP Board Question) try: val = int(input('Enter score: ')) res = 500 / val except Exception as e: print('General error:', e) except ZeroDivisionError: print('Score cannot be zero!') except ValueError: print('Must enter digits only!')
Step-by-Step Solution:

Step 1: Flaw Identification (1 Mark):

The code places the broad parent superclass except Exception: before the specific derived child clauses (ZeroDivisionError and ValueError).

Step 2: Technical Consequence (1 Mark):

Because in Python's object-oriented hierarchy both ZeroDivisionError and ValueError inherit from Exception, the first handler (except Exception:) eagerly intercepts every raised exception. Consequently, the specialized handlers below it become unreachable dead code; the messages 'Score cannot be zero!' and 'Must enter digits only!' can never be printed under any circumstances.

Step 3: Corrected Code (1 Mark):

try:
    val = int(input('Enter score: '))
    res = 500 / val
except ZeroDivisionError:
    # Specific derived subclass 1 listed first
    print('Score cannot be zero!')
except ValueError:
    # Specific derived subclass 2 listed second
    print('Must enter digits only!')
except Exception as e:
    # General parent superclass catch-all placed at bottom
    print('General error:', e)
Example 3
Determine the exact console output produced by the following Python program. Justify the execution sequence of each line printed. (UPMSP Board Question) def compute_ratio(a, b): try: print('Entering Try') ans = a // b print('Calculation Success:', ans) return ans except ZeroDivisionError: print('Handling Division by Zero') return -1 finally: print('Cleanup in Finally') print('Call 1 Result:', compute_ratio(20, 4)) print('Call 2 Result:', compute_ratio(20, 0))
Step-by-Step Solution:

Step 1: Step-by-Step Execution Analysis (1.5 Marks):

  • For Call 1 (compute_ratio(20, 4)):
    1. Enters try, prints Entering Try.
    2. Computes 20 // 4 = 5 without error; prints Calculation Success: 5.
    3. Encounters return 5. Python pauses the return value.
    4. The finally block executes unconditionally; prints Cleanup in Finally.
    5. The paused return delivers 5 to the caller, printing Call 1 Result: 5.
  • For Call 2 (compute_ratio(20, 0)):
    1. Enters try, prints Entering Try.
    2. 20 // 0 raises ZeroDivisionError; calculation line skipped.
    3. Jumps to except ZeroDivisionError; prints Handling Division by Zero.
    4. Encounters return -1. Python pauses the return value.
    5. The finally block executes unconditionally; prints Cleanup in Finally.
    6. The paused return delivers -1 to the caller, printing Call 2 Result: -1.

Step 2: Exact Console Output (1.5 Marks):

Entering Try
Calculation Success: 5
Cleanup in Finally
Call 1 Result: 5
Entering Try
Handling Division by Zero
Cleanup in Finally
Call 2 Result: -1
Example 4
The following Python function reads numerical marks from a text file and computes their average. Refactor the function to handle FileNotFoundError, ValueError (for non-numeric data), and ZeroDivisionError (for empty files), ensuring the file handle is reliably closed in all circumstances. (UPMSP Board Question) def compute_file_average(filepath): f = open(filepath, 'r') lines = f.readlines() total = 0 for line in lines: total += int(line.strip()) avg = total / len(lines) f.close() return avg
Step-by-Step Solution:

Step 1: Identifying Architectural Weaknesses in Original Code (1 Mark):

  • If filepath does not exist, open() throws unhandled FileNotFoundError.
  • If any line contains letters or empty spaces, int() throws unhandled ValueError, leaving the file descriptor open.
  • If the file is completely empty, len(lines) == 0, raising ZeroDivisionError and abandoning the open file.
  • The f.close() call is positioned after risky operations, meaning it will never execute if any error occurs.

Step 2: Refactored Robust Implementation (3 Marks):

def compute_file_average(filepath):
    file_handle = None
    try:
        file_handle = open(filepath, 'r')
        lines = file_handle.readlines()
        
        if not lines:
            raise ZeroDivisionError("File contains no records to compute average.")
            
        total = 0
        for line_num, line in enumerate(lines, start=1):
            cleaned = line.strip()
            if not cleaned:
                continue
            total += int(cleaned)
            
        average = total / len(lines)
        return average

    except FileNotFoundError:
        print(f"ERROR: Target file '{filepath}' could not be located on disk.")
        return None

    except ValueError as val_err:
        print(f"ERROR: Corrupted record detected in file: {val_err}")
        return None

    except ZeroDivisionError as zero_err:
        print(f"ERROR: Cannot compute statistics: {zero_err}")
        return 0.0

    finally:
        # Mandatory cleanup: guaranteed file deallocation
        if file_handle is not None and not file_handle.closed:
            file_handle.close()
            print("[RESOURCE CLEANUP] File handle safely released.")
Example 5
Write a Python function named 'calculate_simple_interest(principal, rate, time)' that defends against invalid inputs. The function must: 1. Raise a TypeError if any argument is non-numeric. 2. Raise a ValueError with a custom message if principal <= 0 or rate < 0 or time <= 0. 3. Include an assert statement verifying that the computed interest is non-negative. Include a test try-except block demonstrating how a caller catches these exceptions. (UPMSP Board Question)
Step-by-Step Solution:

Step 1: Function Implementation with Defensive Checks (2.5 Marks):

def calculate_simple_interest(principal, rate, time):
    # 1. Type validation check
    for param_name, param_val in [('principal', principal), ('rate', rate), ('time', time)]:
        if not isinstance(param_val, (int, float)):
            raise TypeError(f"Parameter '{param_name}' must be numeric. Received: {type(param_val).name}")

    # 2. Domain boundary value checks
    if principal <= 0:
        raise ValueError(f"Principal amount must be positive. Received: {principal}")
    if rate < 0:
        raise ValueError(f"Interest rate cannot be negative. Received: {rate}")
    if time <= 0:
        raise ValueError(f"Time period must be greater than zero. Received: {time}")

    # Calculate interest
    interest = (principal * rate * time) / 100.0

    # 3. Assertion invariant check: computed interest should never be negative
    assert interest >= 0, f"Invariant violation: computed interest is negative ({interest})"

    return interest

Step 2: Caller Demonstration with try-except (1.5 Marks):

test_cases = [
    (10000, 7.5, 2),    # Valid case
    (-5000, 5.0, 1),    # Invalid principal
    (20000, "eight", 3) # Invalid type
]

for p, r, t in test_cases:
    try:
        si = calculate_simple_interest(p, r, t)
        print(f"Principal: Rs. {p}, Rate: {r}%, Time: {t} yrs -> Simple Interest: Rs. {si:.2f}")
    except TypeError as t_err:
        print(f"TYPE ERROR: {t_err}")
    except ValueError as v_err:
        print(f"VALUE ERROR: {v_err}")
    except AssertionError as a_err:
        print(f"ASSERTION FAILURE: {a_err}")
Example 6
Design a complete Python program for the UPMSP Secondary School Registration System that satisfies the following requirements: 1. Create a user-defined custom exception class named 'InvalidAgeError' inheriting from Exception. The class must define an init method that accepts the submitted age and sets an informative message. 2. Create a second custom exception class named 'DuplicateEnrollmentError' inheriting from Exception. 3. Implement a class 'SchoolRegistration' with a list of already registered roll numbers. Implement a method 'register_candidate(roll_no, student_name, age)' that: - Raises InvalidAgeError if age < 14 or age > 20. - Raises DuplicateEnrollmentError if roll_no already exists. 4. Write a test driver demonstrating the registration of at least three candidates, catching and reporting custom exceptions gracefully. (UPMSP 5-Mark Practical Exam Problem)
Step-by-Step Solution:

Step 1: Custom Exception Classes Definition (1.5 Marks):

# Custom Exception 1: Inappropriate Candidate Age
class InvalidAgeError(Exception):
    def init(self, age, min_age=14, max_age=20):
        self.age = age
        self.min_age = min_age
        self.max_age = max_age
        self.msg = f"Candidate age {age} is outside the statutory board window [{min_age}-{max_age} years]."
        super().init(self.msg)

    def str(self):
        return f"[REGISTRATION AGE ERROR] {self.msg}"

# Custom Exception 2: Duplicate Roll Number
class DuplicateEnrollmentError(Exception):
    def init(self, roll_no):
        self.roll_no = roll_no
        self.msg = f"Roll number '{roll_no}' has already been enrolled in the session registry."
        super().init(self.msg)

    def str(self):
        return f"[DUPLICATE ENTRY ERROR] {self.msg}"

Step 2: Domain Class Implementation with Validation (1.5 Marks):

class SchoolRegistration:
    def init(self, school_code, school_name):
        self.school_code = school_code
        self.school_name = school_name
        self.registered_students = {}  # roll_no -> {name, age}

    def register_candidate(self, roll_no, student_name, age):
        # 1. Age boundary enforcement
        if not (14 <= age <= 20):
            raise InvalidAgeError(age)

        # 2. Uniqueness check
        if roll_no in self.registered_students:
            raise DuplicateEnrollmentError(roll_no)

        # 3. Successful registration
        self.registered_students[roll_no] = {'name': student_name, 'age': age}
        print(f"SUCCESS: Enrolled '{student_name}' (Roll No: {roll_no}, Age: {age})")
        return True

Step 3: Complete Driver Code with try-except-finally (2 Marks):

# Instantiate Board Examination Center
center = SchoolRegistration("UP-7201", "Government Inter College, Prayagraj")

candidates_to_register = [
    ("UP12-001", "Pooja Mishra", 17),    # Valid Candidate
    ("UP12-002", "Rohit Kumar", 11),     # Underage (< 14)
    ("UP12-001", "Pooja Mishra", 17),    # Duplicate Roll Number
    ("UP12-003", "Anand Pandey", 23)     # Overage (> 20)
]

for roll, name, age in candidates_to_register:
    print(f"
Processing Application: {name} (Roll: {roll})...")
    try:
        center.register_candidate(roll, name, age)
    except InvalidAgeError as age_err:
        print(f"REJECTED: {age_err}")
    except DuplicateEnrollmentError as dup_err:
        print(f"REJECTED: {dup_err}")
    except Exception as unexpected:
        print(f"UNEXPECTED ERROR: {unexpected}")
    finally:
        print(f"Candidate {roll} verification audit completed.")

Check Your Understanding (Diagnostic Practice Questions)

Diagnostic questions testing core conceptual clarity. Answers are hidden initially — solve each problem first, then click to reveal the step-by-step verified solution.

1
Which built-in exception is raised when executing: 'result = [10, 20, 30][5]'?
Reveal Answer & Explanation
Answer: IndexError: list index out of range. The list contains only indices 0, 1, and 2; requesting index 5 exceeds the bounds of the sequence.
Consider the subclass of LookupError that handles sequence subscript boundaries.
2
What is the consequence of placing 'except ArithmeticError:' before 'except ZeroDivisionError:' in a multi-except construct?
Reveal Answer & Explanation
Answer: Because ZeroDivisionError inherits from ArithmeticError, the parent handler intercepts the division-by-zero error first. The 'except ZeroDivisionError:' clause becomes unreachable dead code.
Recall the relationship between parent superclasses and derived child subclasses in the Python exception tree.
3
In a try-except-else-finally construct, under what exact condition will the statements in the 'else' block execute?
Reveal Answer & Explanation
Answer: The else block executes IF AND ONLY IF the try block ran to completion with zero exceptions raised. If any exception occurs—even if caught and handled—the else block is skipped.
Think about whether any exception occurred inside the try block.
4
Explain what happens when a function encounters a 'return' statement inside its try block while a finally block is also present.
Reveal Answer & Explanation
Answer: Python pauses the return statement, executes all statements inside the finally block completely, and only then delivers the return value back to the caller.
Does finally execute before or after control leaves the function?
5
What is the primary difference between using 'raise ValueError("Invalid")' versus writing an 'assert' statement?
Reveal Answer & Explanation
Answer: 'raise ValueError' is an active runtime instruction that always executes regardless of interpreter flags. In contrast, 'assert' statements can be globally compiled out and disabled when running Python with the -O flag.
Consider what happens when Python is executed with the -O optimization flag.
6
Why should a user-defined custom exception class inherit from Exception rather than BaseException?
Reveal Answer & Explanation
Answer: Standard application exception handlers catch 'except Exception:'. If a custom exception inherits directly from BaseException, it bypasses standard application handlers and behaves like a fatal system exit signal.
Think about how standard application catch-all blocks are written.
Finished Studying This Chapter?
READY TO PRACTICE?

Timed CBT Practice Tests (Exam Simulator)

Put your concepts to the test with official curriculum-aligned Foundation and Advanced practice tests. Get instant accuracy scores, time metrics, and step-by-step verified explanations.

STAY UPDATED

Get the Latest Updates

New test series, syllabus updates, exam notifications and more.

Syllabus
Updates
Exam
Alerts
Study
Resources