Python Unit IV Notes
Python Unit IV Notes
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
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"]
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
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
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
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