0% found this document useful (0 votes)
4 views40 pages

Python Unit IV Notes

This document covers essential file concepts in Python, including absolute and relative paths, file access modes, and reading/writing text files. It emphasizes the importance of understanding file paths for successful file handling and data processing, highlighting best practices such as using relative paths and the 'with' statement for file operations. The document also addresses common errors and provides practical examples for effective file management.

Uploaded by

Vishnuvardan
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views40 pages

Python Unit IV Notes

This document covers essential file concepts in Python, including absolute and relative paths, file access modes, and reading/writing text files. It emphasizes the importance of understanding file paths for successful file handling and data processing, highlighting best practices such as using relative paths and the 'with' statement for file operations. The document also addresses common errors and provides practical examples for effective file management.

Uploaded by

Vishnuvardan
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Lecture 31: File Concepts — Paths (Absolute vs.

Relative)
Understanding file paths is non-negotiable for any real Python program. File handling, data
analysis, configuration loading, logging, and automation all fail if path concepts are unclear. This
lecture establishes how Python locates files and why programs break when paths are written
incorrectly.
1. What is a File Path?
Definition
A file path specifies the location of a file or directory in a file system.
It tells the operating system:
• Where the file is located
• How to reach it from a known reference point
Example:
C:\Users\Admin\data\[Link]
2. Components of a File Path
A file path generally consists of:
1. Root / Base directory
2. Subdirectories
3. File name
4. File extension
Example:
/home/user/project/data/[Link]
3. Absolute Path
Definition
An absolute path specifies the complete path from the root directory to the file.
Key property:
It does not depend on the current working directory.
Examples
Windows
C:\Users\Student\Documents\project\[Link]
Linux / macOS
/home/student/project/[Link]
Python Example
file = open("C:/Users/Student/Documents/project/[Link]", "r")
✔ Always points to the same file
Less portable across systems
4. Relative Path
Definition
A relative path specifies the file location relative to the current working directory (CWD).
Key property:
It depends on where the program is executed from.
Examples
Assume current working directory:
/project
Relative Path Meaning
[Link] File in current directory
data/[Link] File inside data folder
../config/[Link] One level above
Python Example
file = open("data/[Link]", "r")
✔ Portable
✔ Recommended for projects
Fails if CWD is misunderstood
5. Current Working Directory (CWD)
Definition
The current working directory is the directory from which the Python script is executed, not
where it is saved.
Checking CWD in Python
import os
print([Link]())
This is the reference point for relative paths.
6. Absolute vs Relative Path (Critical Comparison)
Feature Absolute Path Relative Path
Starting point Root directory Current working directory
Portability Low High
Readability Long Short
Dependency on CWD No Yes
Preferred in projects ✔
7. Path Separators (OS-Specific Issue)
Windows
C:\Users\Student\[Link]
⚠ Backslash is an escape character in Python.
Correct usage:
"C:\\Users\\Student\\[Link]"
OR
r"C:\Users\Student\[Link]"
Linux / macOS
/home/student/[Link]
Uses forward slash / (no escape issue).
8. Best Practice: Use [Link]
Python provides OS-independent path handling.
import os

path = [Link]("data", "[Link]")


Benefits:
• Platform independent
• Cleaner code
• Avoids slash confusion
9. Common Errors (Must Be Explicitly Warned)
1. Assuming script location = working directory
2. Hardcoding absolute paths
3. Using wrong slashes in Windows
4. Forgetting escape characters
5. Running code from IDE vs terminal inconsistently
Typical failure:
FileNotFoundError: [Errno 2] No such file or directory
10. Engineering & Real-World Use Cases
• Reading CSV files for data analysis
• Loading configuration files
• Writing simulation outputs
• Managing project directory structures
• Automating batch file processing
11. Recommended Project Structure (Illustrative)
project/

├── data/
│ └── [Link]
├── src/
│ └── [Link]
├── output/
│ └── [Link]
Access from src/[Link]:
file = open("../data/[Link]", "r")
12. Best Practices (Engineering Discipline)
1. Prefer relative paths inside projects
2. Print and verify CWD during debugging
3. Use [Link]() for portability
4. Avoid hardcoded system-specific paths
5. Document directory structure clearly
13. Summary (Exam-Ready Points)
• File paths specify file locations
• Absolute paths start from root directory
• Relative paths depend on current working directory
• Relative paths improve portability
• CWD determines how relative paths are resolved
• OS path separators differ
• [Link] ensures cross-platform compatibility
Lecture 32: File Access Modes and Opening/Closing in Python
This lecture moves from path concepts (Lecture 31) to actual file interaction. Correct
handling of file access modes and proper opening/closing of files is critical for data integrity,
resource management, and program reliability. Most file-related bugs in student programs
arise from misunderstanding these basics.
1. Why File Access Modes Matter
When a file is opened, Python must know:
• What operation you intend to perform (read, write, append)
• How existing data should be treated
• Whether the file must already exist or be created
Wrong mode → data loss, runtime errors, or silent failures.
2. Opening a File in Python
Basic Syntax
file_object = open(filename, mode)
• filename → path to the file (absolute or relative)
• mode → access mode
• Returns a file object
Example
f = open("[Link]", "r")
3. Common File Access Modes (CORE CONTENT)
3.1 Read Mode ("r")
• Opens file for reading
• File must exist
• Default mode if not specified
f = open("[Link]", "r")
Error if file does not exist:
FileNotFoundError
3.2 Write Mode ("w")
• Opens file for writing
• Creates file if it does not exist
• Overwrites file if it already exists
f = open("[Link]", "w")
⚠ Dangerous mode: existing data is erased.
3.3 Append Mode ("a")
• Opens file for writing at the end
• Creates file if it does not exist
• Preserves existing content
f = open("[Link]", "a")
Used for:
• Logging
• Incremental data storage
3.4 Exclusive Creation Mode ("x")
• Creates a new file
• Fails if file already exists
f = open("[Link]", "x")
Raises:
FileExistsError
4. Text vs Binary Modes (Introductory)
Text Mode (Default)
open("[Link]", "r")
Binary Mode
open("[Link]", "rb")
open("[Link]", "wb")
Binary modes are required for:
• Images
• Audio
• Executables
(Advanced handling covered later.)
5. Summary Table of File Modes (Exam-Favourite)
Mode Meaning File Exists? Data Loss?
r Read Must exist No
w Write Created/Overwritten ✔ Yes
a Append Created if missing No
x Create Must not exist No
rb Read binary Must exist No
wb Write binary Created/Overwritten ✔ Yes
6. Closing a File (close())
Why Closing Is Mandatory
• Releases system resources
• Flushes buffered data to disk
• Prevents file corruption
• Avoids file-lock issues
Syntax
[Link]()
Example
f = open("[Link]", "r")
content = [Link]()
[Link]()
⚠ Forgetting to close files can:
• Leak resources
• Lock files
• Lose written data
7. The with Statement (BEST PRACTICE)
Problem with Manual Closing
f = open("[Link]", "r")
data = [Link]()
# If error occurs here → file not closed
[Link]()
Solution: Context Manager (with)
with open("[Link]", "r") as f:
data = [Link]()
✔ File closes automatically
✔ Even if an error occurs
✔ Cleaner and safer
8. Writing to Files (Illustrative)
with open("[Link]", "w") as f:
[Link]("Python File Handling\n")
Append example:
with open("[Link]", "a") as f:
[Link]("New line added\n")
9. Common Errors (Must Be Explicitly Warned)
1. Using "w" instead of "a" (data loss)
2. Forgetting to close files
3. Reading from a file opened in write mode
4. Writing to a file opened in read mode
5. Assuming files close automatically without with
Incorrect:
f = open("[Link]", "r")
[Link]("Hello") # Error
10. Engineering & Real-World Use Cases
• Reading CSV files for analysis
• Writing simulation outputs
• Logging program execution
• Storing configuration settings
• Handling batch processing results
11. Best Practices (Engineering Discipline)
1. Always use with open(...) syntax
2. Double-check file modes before running code
3. Never use "w" unless overwriting is intended
4. Separate read and write logic clearly
5. Handle files using relative paths in projects
12. Relationship to Lecture 31
Lecture 31 Lecture 32
Where the file is How the file is accessed
Paths Modes
Location Operation
CWD awareness Data safety
Both must be understood together.
13. Summary (Exam-Ready Points)
• Files are opened using open(filename, mode)
• Access modes control file behavior
• r, w, a, x are primary text modes
• w overwrites existing data
• Files must be closed after use
• with ensures automatic file closing
• Correct mode selection prevents data loss
Lecture 33: Reading and Writing Text to Files
This lecture completes the file-handling triad:
• Lecture 31 → Where files are located (paths)
• Lecture 32 → How files are opened (modes)
• Lecture 33 → How data is actually read from and written to files
Correct reading and writing of text files is fundamental for data processing, reporting, logging,
configuration management, and automation. Most real-world Python programs interact with
files at this level.
1. What Does “Reading and Writing Text” Mean?
Text file operations involve:
• Reading characters or lines from a file
• Writing strings to a file
• Preserving formatting (newlines, spaces)
• Handling files safely and efficiently
Text files include:
• .txt
• .csv
• .log
• .dat
• .cfg
2. Reading Text from Files
Python provides three primary methods to read text files:
Method Purpose
read() Read entire file
readline() Read one line at a time
readlines() Read all lines into a list
3. read() — Read Entire File
Syntax
[Link](size)
• Reads the entire file as a single string
• Optional size specifies number of characters
Example
with open("[Link]", "r") as f:
content = [Link]()

print(content)
Characteristics
• Returns a string
• Suitable for small files
• Not memory-efficient for large files
4. readline() — Read One Line at a Time
Syntax
[Link]()
Example
with open("[Link]", "r") as f:
line1 = [Link]()
line2 = [Link]()

print(line1)
print(line2)
Characteristics
• Reads one line per call
• Includes newline character \n
• Efficient for sequential processing
Used in:
• Log file analysis
• Streaming data processing
5. readlines() — Read All Lines into a List
Syntax
[Link]()
Example
with open("[Link]", "r") as f:
lines = [Link]()

print(lines)
Output:
['Line 1\n', 'Line 2\n', 'Line 3\n']
Characteristics
• Returns a list of strings
• Each element is one line
• Suitable for moderate-sized files
6. Iterating Directly Over a File (Best Practice)
Python files are iterable objects.
with open("[Link]", "r") as f:
for line in f:
print([Link]())
✔ Memory efficient
✔ Clean and readable
✔ Preferred for large files
7. Writing Text to Files
7.1 write() — Write a String
Syntax
[Link](string)
Example
with open("[Link]", "w") as f:
[Link]("Python File Handling\n")
[Link]("Lecture 33\n")
⚠ write():
• Does not add newline automatically
• Returns number of characters written
8. writelines() — Write Multiple Lines
Syntax
[Link](iterable)
Example
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]

with open("[Link]", "w") as f:


[Link](lines)
⚠ Newlines must be explicitly included.
9. Append Mode ("a") in Writing
with open("[Link]", "a") as f:
[Link]("New entry added\n")
Use cases:
• Logging
• Incremental result storage
• Audit trails
10. Reading vs Writing (Critical Comparison)
Aspect Reading Writing
Mode r w, a
Aspect Reading Writing
Operation Extract data Store data
Data type String / List String

Data loss risk No ✔ Yes (w)


Typical use Analysis Reporting
11. Common Errors (Must Be Explicitly Warned)
1. Forgetting newline characters when writing
2. Using read() on very large files
3. Mixing read and write without proper modes
4. Writing to files opened in "r" mode
5. Forgetting .strip() when processing lines
Incorrect:
with open("[Link]", "r") as f:
[Link]("Hello") # Error
12. Engineering & Real-World Use Cases
• Reading measurement data from text/CSV files
• Writing simulation outputs
• Processing log files
• Generating reports
• Batch file processing
Example:
with open("[Link]", "w") as f:
for value in results:
[Link](f"{value}\n")
13. Best Practices (Engineering Discipline)
1. Use with open() always
2. Prefer file iteration for large files
3. Use "a" for logs, "w" for reports
4. Validate file existence before reading
5. Handle newlines explicitly
6. Keep file I/O separate from computation logic
14. Summary (Exam-Ready Points)
• read() reads entire file as a string
• readline() reads one line at a time
• readlines() returns list of lines
• Files can be iterated line by line
• write() writes strings to files
• writelines() writes multiple strings
• Newlines must be explicitly managed
• Proper file modes prevent data loss
Lecture 34: Processing Files Line-by-Line
This lecture addresses efficient and safe file processing, especially for large text files. Reading
entire files into memory is often impractical or dangerous. Line-by-line processing is the
industry-standard approach for logs, datasets, configuration files, and streaming-style
workflows.
This lecture builds directly on:
• Lecture 31 → File paths
• Lecture 32 → File modes and opening/closing
• Lecture 33 → Reading and writing text
1. Why Line-by-Line Processing is Necessary
Problems with Reading Entire Files
• High memory consumption
• Poor performance for large files
• Risk of program crash
• Unnecessary data loading
Key idea:
Process data incrementally, not all at once.
2. File Objects are Iterable
In Python, a file object can be used directly in a for loop.
with open("[Link]", "r") as f:
for line in f:
print(line)
This reads one line at a time, automatically.
✔ Memory efficient
✔ Clean syntax
✔ Preferred approach
3. Basic Line-by-Line Processing Pattern
Standard Template (Must Memorize)
with open("[Link]", "r") as f:
for line in f:
process(line)
Where:
• line is a string
• Each line includes a trailing newline \n
4. Handling Newline Characters (\n)
Lines read from files usually end with \n.
Problem
print(line)
Results in extra blank lines.
Solution: Use strip() or rstrip()
for line in f:
clean_line = [Link]()
print(clean_line)
Difference:
• strip() → removes leading & trailing whitespace
• rstrip() → removes trailing whitespace only
5. Filtering Lines While Reading
Example: Skip Empty Lines
with open("[Link]", "r") as f:
for line in f:
line = [Link]()
if line == "":
continue
print(line)
Example: Skip Comment Lines
with open("[Link]", "r") as f:
for line in f:
line = [Link]()
if [Link]("#"):
continue
print(line)
6. Processing Data Line-by-Line
Example: Convert File Data to Numbers
with open("[Link]", "r") as f:
for line in f:
value = float([Link]())
print(value * 2)
Used in:
• Sensor data processing
• Numerical analysis
• Batch computations
7. Line-by-Line with Conditional Logic
with open("[Link]", "r") as f:
for line in f:
if "ERROR" in line:
print([Link]())
Use cases:
• Log file scanning
• Pattern detection
• Validation checks
8. Writing While Reading (Pipeline Pattern)
Example: Read → Process → Write
with open("[Link]", "r") as fin, open("[Link]", "w") as
fout:
for line in fin:
[Link]([Link]())
✔ Streaming pipeline
✔ No full file in memory
✔ Efficient and safe
9. Counting and Aggregation While Reading
Example: Count Lines
count = 0
with open("[Link]", "r") as f:
for _ in f:
count += 1
print(count)
Example: Sum Values
total = 0
with open("[Link]", "r") as f:
for line in f:
total += int([Link]())
print(total)
10. Common Student Errors (Must Be Explicitly Corrected)
1. Using read() for large files
2. Forgetting to remove newline characters
3. Ignoring empty or malformed lines
4. Mixing read/write logic without clarity
5. Not using with statement
Incorrect:
data = [Link]().split("\n") # memory heavy
Correct:
for line in f:
process(line)
11. Engineering & Real-World Use Cases
• Log file analysis
• Processing large CSV files
• Reading simulation outputs
• Batch processing datasets
• Stream-based data pipelines
12. Best Practices (Engineering Discipline)
1. Always process large files line-by-line
2. Use with open() for safety
3. Strip newline characters early
4. Validate each line before processing
5. Keep file I/O separate from logic
6. Use streaming pipelines when possible
13. Performance Perspective (Conceptual)
Method Memory Use Suitability
read() High Small files only
readlines() Moderate Medium files
for line in file Low Large files ✔
14. Summary (Exam-Ready Points)
• File objects are iterable
• Line-by-line reading is memory efficient
• Each line contains newline characters
• strip() removes unwanted whitespace
• Conditional logic can filter lines
• Reading and writing can be combined
• Line-by-line processing is industry standard
Lecture 35: Introduction to Exceptions and Runtime Errors
This lecture introduces runtime errors and exceptions, which are unavoidable in real-world
programs—especially when dealing with files, user input, numerical computations, and
external data. Robust Python programs are not those that never fail, but those that fail
gracefully.
This lecture lays the foundation for exception handling (try–except), which will be
expanded in the next lecture.
1. What is an Error?
Definition
An error is a problem that prevents a program from executing correctly.
Python errors fall into two broad categories:
1. Syntax Errors
2. Runtime Errors (Exceptions)
2. Syntax Errors vs Runtime Errors
Syntax Errors
• Detected before execution
• Caused by incorrect grammar
• Program does not start
Example:
if x > 5
print(x)
Error:
SyntaxError: invalid syntax
Runtime Errors (Exceptions)
• Occur during execution
• Program starts but crashes at runtime
• Triggered by illegal operations
Example:
x = 10 / 0
Error:
ZeroDivisionError
3. What is an Exception?
Definition
An exception is a runtime error event that disrupts the normal flow of a program.
Key idea:
Exceptions are Python’s way of signaling that something unexpected or invalid occurred during
execution.
4. Why Exceptions Must Be Handled
Without handling:
• Program terminates abruptly
• User sees cryptic error messages
• Files may remain open
• Data may be corrupted
With handling:
• Program continues safely
• Errors are controlled
• Users receive meaningful feedback
5. Common Runtime Errors (Exam-Critical)
5.1 ZeroDivisionError
x = 10 / 0
Occurs when dividing by zero.
5.2 ValueError
int("abc")
Occurs when type conversion fails.
5.3 TypeError
"10" + 5
Occurs when incompatible types are used.
5.4 IndexError
lst = [1, 2, 3]
lst[5]
Occurs when accessing invalid index.
5.5 KeyError
d = {"a": 1}
d["b"]
Occurs when dictionary key is missing.
5.6 FileNotFoundError
open("[Link]", "r")
Occurs when file does not exist.
6. Execution Flow During a Runtime Error
Program starts
|
Normal execution
|
Exception occurs
|
Program terminates (if unhandled)
This abrupt termination is unacceptable in production code.
7. Simple Demonstration (Uncaught Exception)
print("Start")
x = int(input("Enter number: "))
y = 10 / x
print("Result:", y)
print("End")
If input is 0:
• Program crashes
• "End" is never printed
8. Error Messages: How to Read Them
Typical error message structure:
Traceback (most recent call last):
File "[Link]", line 3, in <module>
x = 10 / 0
ZeroDivisionError: division by zero
Key parts:
• File name
• Line number
• Error type
• Error description
Students must learn to read, not fear, error messages.
9. When Do Runtime Errors Commonly Occur?
• User input handling
• File operations
• Mathematical calculations
• Data parsing
• List and dictionary access
• External resources
10. Preventing vs Handling Errors
Prevention (Limited)
• Input validation
• Conditional checks
Example:
if x != 0:
y = 10 / x
Handling (Robust)
• Use exception handling (try–except)
• Works even for unexpected conditions
(Full handling introduced in the next lecture.)
11. Common Student Mistakes (Must Be Explicitly Corrected)
1. Ignoring runtime errors
2. Assuming code will always receive valid input
3. Misinterpreting error messages
4. Confusing syntax errors with exceptions
5. Letting programs crash without explanation
12. Engineering & Real-World Context
Runtime errors are inevitable in:
• File-based data pipelines
• Sensor data ingestion
• Numerical modeling
• User-facing tools
• Automation scripts
Professional code must:
• Detect errors
• Report them meaningfully
• Recover where possible
13. Best Practices (Engineering Discipline)
1. Anticipate failure points
2. Read error messages carefully
3. Never ignore runtime errors
4. Separate core logic from risky operations
5. Prepare for invalid inputs and missing files
14. Summary (Exam-Ready Points)
• Syntax errors stop execution before runtime
• Runtime errors occur during execution
• Exceptions represent runtime errors in Python
• Common exceptions include ZeroDivisionError, ValueError, TypeError
• Unhandled exceptions terminate programs
• Exception awareness is essential for robust programs
Lecture 36: Handling Exceptions — try, except, and raise
This lecture converts the awareness of runtime errors (Lecture 35) into controlled,
professional error handling. Proper exception handling ensures programs do not crash,
protect data, and communicate failures clearly—a requirement in engineering, data pipelines,
and automation.
1. Why Exception Handling is Essential
Uncontrolled runtime errors cause:
• Abrupt program termination
• File corruption (open handles not closed)
• Loss of user trust
• Unreliable automation
Key principle:
Anticipate failure and handle it explicitly.
2. The try–except Construct
Purpose
Wrap risky code so exceptions can be caught and handled without stopping the program.
Basic Syntax
try:
risky_statements
except ExceptionType:
handling_statements
Execution logic:
1. Python executes the try block
2. If no error → except is skipped
3. If an exception occurs → control jumps to except
3. Simple Example
try:
x = int(input("Enter a number: "))
y = 10 / x
print("Result:", y)
except ZeroDivisionError:
print("Division by zero is not allowed")
Behavior:
• Input 0 → handled gracefully
• Program does not crash
4. Catching Multiple Exceptions
Using Multiple except Blocks
try:
value = int(input("Enter value: "))
result = 100 / value
except ValueError:
print("Invalid number format")
except ZeroDivisionError:
print("Cannot divide by zero")
Rule:
• Python checks except blocks top to bottom
• First matching block executes
5. Generic Exception Handling (Use Carefully)
try:
risky_operation()
except Exception:
print("An unexpected error occurred")
⚠ Warning:
• Masks the real error if overused
• Use only for last-resort safety, not normal logic
6. Using else with try–except (Best Practice)
Purpose
Execute code only if no exception occurs.
try:
x = int(input("Enter number: "))
y = 10 / x
except ZeroDivisionError:
print("Division by zero")
except ValueError:
print("Invalid input")
else:
print("Computation successful:", y)
Benefit:
• Keeps normal logic separate from error handling
7. The finally Block (Critical for Resources)
Purpose
Executes always, whether an exception occurs or not.
try:
f = open("[Link]", "r")
content = [Link]()
except FileNotFoundError:
print("File not found")
finally:
print("Closing file if open")
Used for:
• Closing files
• Releasing resources
• Cleanup operations
8. Raising Exceptions Explicitly (raise)
Why raise is Needed
Sometimes you detect an invalid condition yourself and must signal it as an error.
Syntax
raise ExceptionType("error message")
Example: Input Validation
age = int(input("Enter age: "))

if age < 0:
raise ValueError("Age cannot be negative")

print("Age accepted")
This:
• Stops execution
• Provides a meaningful message
• Forces caller to handle the issue
9. Raising Exceptions Inside Functions (Professional Pattern)
def withdraw(balance, amount):
if amount > balance:
raise ValueError("Insufficient balance")
return balance - amount

try:
withdraw(5000, 7000)
except ValueError as e:
print("Transaction failed:", e)
✔ Business logic stays clean
✔ Error handling stays external
10. Re-raising Exceptions
Used when you want to:
• Log an error
• Then propagate it upward
try:
process_data()
except Exception:
print("Logging error")
raise
11. Common Student Errors (Must Be Corrected)
1. Wrapping entire program in one try
2. Catching Exception everywhere
3. Using exceptions instead of normal condition checks
4. Forgetting meaningful error messages
5. Ignoring the raised exception
Incorrect:
try:
x = 10 / 0
except:
pass # hides serious error
12. Engineering & Real-World Use Cases
• File handling (missing files, permission errors)
• User input validation
• Data parsing pipelines
• Numerical modeling safeguards
• API and automation scripts
13. Best Practices (Engineering Discipline)
1. Catch specific exceptions, not generic ones
2. Use else for clean success logic
3. Use finally for cleanup
4. Raise exceptions for invalid states
5. Never suppress errors silently
6. Keep try blocks minimal
14. Summary (Exam-Ready Points)
• try contains risky code
• except handles specific exceptions
• Multiple except blocks are allowed
• else runs when no exception occurs
• finally always executes
• raise explicitly triggers an exception
• Proper handling prevents crashes and data loss
Lecture 37: Detailed Exception Nuances and Catching Multiple Errors
This lecture deepens exception handling by focusing on how exceptions propagate, how
multiple errors are caught correctly, and how to write precise, maintainable handlers. The
goal is not merely to prevent crashes, but to diagnose, recover, and communicate failures
accurately—a requirement in production-grade Python, data pipelines, and engineering tools.
1. Exception Hierarchy (Why Order Matters)
Python exceptions form a class hierarchy. More specific exceptions inherit from more general
ones.
Simplified view:
BaseException
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── ValueError
├── TypeError
├── IOError / OSError
│ └── FileNotFoundError
Rule (Exam-Critical):
Always catch specific exceptions before generic ones.
Incorrect:
try:
x = 10 / 0
except Exception:
print("Generic error")
except ZeroDivisionError:
print("Divide by zero")
Correct:
try:
x = 10 / 0
except ZeroDivisionError:
print("Divide by zero")
except Exception:
print("Generic error")
2. Catching Multiple Exceptions (Single Block)
Syntax
except (ExceptionType1, ExceptionType2):
handling_code
Example
try:
x = int(input("Enter number: "))
y = 100 / x
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero")
Use this when:
• Recovery logic is the same
• You do not need separate messages
3. Multiple except Blocks (Different Recovery Paths)
try:
value = int(input("Enter value: "))
result = 100 / value
except ValueError:
print("Input must be an integer")
except ZeroDivisionError:
print("Cannot divide by zero")
except Exception:
print("Unexpected error")
Best practice:
• Specific → General
• Clear, user-focused messages
4. Capturing the Exception Object (as e)
Purpose
Access the actual error message for logging or debugging.
try:
open("[Link]", "r")
except FileNotFoundError as e:
print("Error details:", e)
Benefits:
• Precise diagnostics
• Better logs
• Easier debugging
5. Exception Propagation (Call Stack Behavior)
If an exception is not handled in the current scope, it propagates upward.
def read_file():
return open("[Link]", "r")
def process():
read_file()

process()
If [Link] is missing:
• Error propagates from read_file() → process() → main program
• Program terminates unless caught
Controlled Propagation
try:
process()
except FileNotFoundError:
print("File missing at higher level")
6. Re-raising Exceptions (Advanced but Important)
Used when you want to:
1. Handle partially (e.g., log)
2. Then propagate the same error
try:
risky_operation()
except Exception as e:
print("Logging error:", e)
raise
⚠ raise without arguments re-throws the same exception.
7. Exception Chaining (raise ... from ...)
Purpose
Preserve original error context when raising a new exception.
try:
int("abc")
except ValueError as e:
raise RuntimeError("Conversion failed") from e
This creates a linked traceback:
• Original cause is not lost
• Debugging is significantly easier
8. Avoiding Over-Broad Exception Handling
Dangerous Pattern
try:
risky_code()
except Exception:
pass
Why this is bad:
• Silences critical bugs
• Masks logic errors
• Makes debugging nearly impossible
Correct mindset:
If you catch it, you must handle or report it meaningfully.
9. Exceptions vs Normal Control Flow
Exceptions are not replacements for conditionals.
Incorrect:
try:
if x < 0:
raise ValueError
except ValueError:
print("Negative")
Correct:
if x < 0:
print("Negative")
Use exceptions for:
• Truly exceptional conditions
• External failures (files, input, I/O)
10. Exception Handling with Context Managers
Using with ensures cleanup even if exceptions occur.
try:
with open("[Link]", "r") as f:
content = [Link]()
except FileNotFoundError:
print("File not found")
✔ File is closed automatically
✔ No resource leak
11. Common Student Errors (Must Be Explicitly Corrected)
1. Catching Exception everywhere
2. Incorrect ordering of except blocks
3. Suppressing errors silently
4. Overusing exceptions for logic control
5. Ignoring exception messages
12. Engineering & Real-World Use Cases
• Robust file-processing pipelines
• Validation-heavy data ingestion
• Numerical simulation safeguards
• Automation and scripting
• User-facing tools with graceful failure
13. Best Practices (Engineering Discipline)
1. Catch specific exceptions first
2. Group exceptions only when recovery is identical
3. Use as e for logging and diagnostics
4. Re-raise exceptions when higher layers must decide
5. Never suppress exceptions silently
6. Keep try blocks small and focused
14. Summary (Exam-Ready Points)
• Python exceptions follow a hierarchy
• Order of except blocks matters
• Multiple exceptions can be caught together
• Exception objects provide error details
• Unhandled exceptions propagate up the call stack
• raise rethrows exceptions
• Exception chaining preserves root causes
• Broad exception handling is dangerous
Lecture 38: Cleanup Actions with the finally Block
This lecture focuses on guaranteed cleanup, a critical concept in robust programming.
Regardless of whether a program succeeds, fails, or raises an exception, certain actions must
always execute—closing files, releasing resources, and restoring system state. The finally
block exists precisely for this purpose.
This lecture completes the exception-handling lifecycle:
• Lecture 35 → Runtime errors and exceptions
• Lecture 36 → try, except, raise
• Lecture 37 → Multiple exceptions and nuanced catching
• Lecture 38 → Cleanup using finally
1. Why Cleanup is Non-Negotiable
If cleanup is skipped:
• Files remain open
• Memory and system resources leak
• Data may not be flushed to disk
• Programs behave unpredictably
Key principle:
Cleanup must occur even when failure happens.
2. What is the finally Block?
Definition
The finally block is a section of code that always executes, whether:
• an exception occurs or not
• the exception is handled or not
• a return statement is executed
3. Syntax of try–except–finally
try:
risky_code
except ExceptionType:
handling_code
finally:
cleanup_code
Execution guarantee:
• finally always runs
4. Execution Flow (Exam-Critical)
try block executes
|
Exception occurs? ── No ──► skip except
| Yes
except executes
|
finally executes (always)
5. Simple Demonstration
try:
x = int(input("Enter a number: "))
y = 10 / x
except ZeroDivisionError:
print("Division by zero")
finally:
print("Cleanup complete")
Output (input = 0):
Division by zero
Cleanup complete
Output (input = 2):
Cleanup complete
✔ finally executes in both cases
6. finally with File Handling (Core Use Case)
Without finally (Risky)
f = open("[Link]", "r")
content = [Link]()
# crash here → file remains open
[Link]()
With finally (Safe)
try:
f = open("[Link]", "r")
content = [Link]()
except FileNotFoundError:
print("File not found")
finally:
if 'f' in locals():
[Link]()
✔ File closed even if an error occurs
7. finally vs with Statement
Aspect finally with

Cleanup guarantee ✔ Yes ✔ Yes


Code length Longer Shorter
Recommended for files ✔

General cleanup ✔
Best practice:
• Use with for files
• Use finally for general cleanup
8. finally with return Statements (Important Nuance)
def compute(x):
try:
return 10 / x
except ZeroDivisionError:
return 0
finally:
print("Function exiting")
Even when return executes:
• finally still runs
9. finally Without except
Valid and sometimes necessary.
try:
process_data()
finally:
cleanup_resources()
Use when:
• You want cleanup
• You do not want to suppress exceptions
10. What Should Go Inside finally?
✔ Appropriate:
• Closing files
• Releasing locks
• Closing database connections
• Cleaning temporary files
• Restoring system state
Inappropriate:
• Core business logic
• Complex computations
• Error masking
11. Common Student Errors (Must Be Corrected)
1. Assuming finally runs only on errors
2. Placing normal logic inside finally
3. Using finally instead of except
4. Forgetting cleanup entirely
5. Believing finally prevents crashes
Important:
finally does not handle exceptions—it only cleans up.
12. Engineering & Real-World Use Cases
• File and database access
• Network socket handling
• Resource-intensive simulations
• Automation scripts
• Batch data pipelines
13. Best Practices (Engineering Discipline)
1. Use finally for mandatory cleanup
2. Keep finally blocks short and safe
3. Never suppress exceptions in finally
4. Prefer with when available
5. Treat cleanup as part of system reliability
14. Summary (Exam-Ready Points)
• finally executes regardless of exceptions
• Used for cleanup actions
• Runs even with return
• Can exist without except
• Ensures resource safety
• Does not catch or suppress errors
• Complements try–except handling
Lecture 39: User-defined Exception Classes
This lecture elevates exception handling from reactive error catching to intentional error
design. User-defined exception classes allow programmers to model domain-specific failure
conditions, enforce business rules, and communicate errors clearly and consistently. This is a
hallmark of professional and production-quality Python code.
This lecture builds on:
• Lecture 35 → Runtime errors and exceptions
• Lecture 36 → try, except, raise
• Lecture 37 → Exception hierarchy and multiple catching
• Lecture 38 → Cleanup with finally
1. Why User-defined Exceptions Are Needed
Built-in exceptions (ValueError, TypeError, etc.) are generic.
They often fail to express domain meaning.
Example problem:
raise ValueError("Invalid balance")
Question:
• Invalid how?
• Business rule violation or technical issue?
User-defined exceptions solve this ambiguity.
Key idea:
Use custom exceptions to represent logical or domain-specific errors, not technical failures.
2. What is a User-defined Exception?
Definition
A user-defined exception is a custom exception class created by the programmer, typically by
inheriting from Exception.
class MyError(Exception):
pass
3. Syntax for Creating a Custom Exception
Basic Form
class CustomError(Exception):
pass
With Error Message
class InvalidAgeError(Exception):
def __init__(self, message):
super().__init__(message)
Key rules:
1. Must inherit from Exception (or its subclasses)
2. Class name should follow PascalCase
3. Name should describe the error meaning clearly
4. Raising a User-defined Exception
Example: Input Validation
class NegativeAgeError(Exception):
pass

age = int(input("Enter age: "))

if age < 0:
raise NegativeAgeError("Age cannot be negative")
Behavior:
• Program raises a semantic error
• Message clearly explains the issue
5. Catching User-defined Exceptions
try:
age = int(input("Enter age: "))
if age < 0:
raise NegativeAgeError("Invalid age entered")
except NegativeAgeError as e:
print("Validation error:", e)
✔ Clean separation between:
• Error detection
• Error handling
6. Custom Exceptions Inside Functions (Professional Pattern)
class InsufficientBalanceError(Exception):
pass

def withdraw(balance, amount):


if amount > balance:
raise InsufficientBalanceError("Withdrawal exceeds
balance")
return balance - amount

try:
withdraw(5000, 8000)
except InsufficientBalanceError as e:
print("Transaction failed:", e)
Why this is important:
• Function logic remains clean
• Caller decides how to handle failure
• Code becomes reusable and testable
7. Custom Exception Hierarchies (Advanced but Important)
Custom exceptions can form their own hierarchy.
class ApplicationError(Exception):
pass

class ValidationError(ApplicationError):
pass

class DataError(ApplicationError):
pass
Usage:
raise ValidationError("Invalid input")
Catching:
except ApplicationError:
print("Application-level error")
✔ Enables grouped handling
✔ Mirrors real-world software design
8. When to Use Custom Exceptions (Decision Rule)
Use user-defined exceptions when:
• Error represents a business rule violation
• Error has domain meaning
• Error needs to be distinguished from technical failures
• API or library is being designed
Do not use when:
• A built-in exception already fits perfectly
• Error is trivial or self-explanatory
9. raise with Custom Exceptions and Chaining
try:
int("abc")
except ValueError as e:
raise DataError("Data conversion failed") from e
Benefit:
• Original cause preserved
• Better debugging and traceability
10. Custom Exceptions vs Error Codes
Aspect Error Codes Custom Exceptions
Readability Low High
Flow control Manual Automatic
Debugging Difficult Clear
Pythonic ✔
Python strongly favors exceptions over return-code checking.
11. Common Student Errors (Must Be Explicitly Corrected)
1. Creating exceptions without inheriting from Exception
2. Using vague names (MyError, TestError)
3. Raising custom exceptions for trivial conditions
4. Catching custom exceptions with generic Exception
5. Writing logic inside exception classes
Incorrect:
class Error:
pass # Not an exception
Correct:
class Error(Exception):
pass
12. Engineering & Real-World Use Cases
• Banking and transaction systems
• Input validation frameworks
• Data ingestion pipelines
• Scientific computation constraints
• APIs and libraries
• Configuration validation
13. Best Practices (Engineering Discipline)
1. Name exceptions after what went wrong
2. Inherit from Exception, not BaseException
3. Keep exception classes lightweight
4. Raise exceptions where the error occurs
5. Handle exceptions at appropriate abstraction levels
6. Document custom exceptions clearly
14. Summary (Exam-Ready Points)
• User-defined exceptions model domain-specific errors
• Created by inheriting from Exception
• Raised using raise
• Caught like built-in exceptions
• Improve clarity, robustness, and maintainability
• Support custom exception hierarchies
• Essential for professional-grade Python programs

You might also like