Unit 3: File Handling, Packaging, and Debugging
Topics Covered:
3.1 File Handling - Reading and Writing Files, Exception Handling
Opening Files
Python uses the open() function to work with files. The basic syntax is:
file = open("filename", "mode")
File Modes:
| Mode | Description |
|---|---|
| 'r' | Read (default) - Opens file for reading |
| 'w' | Write - Creates new file or overwrites existing |
| 'a' | Append - Adds content at the end of file |
| 'x' | Create - Creates new file, fails if exists |
| 'r+' | Read and Write |
| 'b' | Binary mode (e.g., 'rb', 'wb') |
| 't' | Text mode (default) |
Reading Files
# Reading entire file
file = open("example.txt", "r")
content = file.read()
print(content)
file.close()
# Reading line by line
file = open("example.txt", "r")
for line in file:
print(line.strip())
file.close()
# Reading specific number of characters
file = open("example.txt", "r")
content = file.read(10) # Read first 10 characters
print(content)
file.close()
# Reading all lines into a list
file = open("example.txt", "r")
lines = file.readlines()
print(lines)
file.close()
Writing Files
# Writing to a file (overwrites existing content)
file = open("output.txt", "w")
file.write("Hello, World!\n")
file.write("This is a new line.")
file.close()
# Appending to a file
file = open("output.txt", "a")
file.write("\nThis line is appended.")
file.close()
# Writing multiple lines
lines = ["Line 1\n", "Line 2\n", "Line 3\n"]
file = open("output.txt", "w")
file.writelines(lines)
file.close()
Using 'with' Statement (Context Manager)
The with statement automatically handles file closing:
# Reading with 'with' statement
with open("example.txt", "r") as file:
content = file.read()
print(content)
# File is automatically closed here
# Writing with 'with' statement
with open("output.txt", "w") as file:
file.write("Hello, World!")
Exception Handling
Python uses try-except blocks to handle errors gracefully:
try:
# Code that might raise an exception
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero!")
# Multiple exceptions
try:
file = open("nonexistent.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found!")
except PermissionError:
print("Permission denied!")
except Exception as e:
print(f"An error occurred: {e}")
try-except-else-finally
try:
file = open("example.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found!")
else:
# Executes if no exception occurred
print("File read successfully!")
print(content)
finally:
# Always executes (cleanup code)
print("Execution completed.")
if 'file' in locals():
file.close()
Raising Exceptions
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative!")
if age < 18:
raise Exception("Must be 18 or older!")
return "Access granted"
try:
result = check_age(-5)
except ValueError as e:
print(f"Value Error: {e}")
except Exception as e:
print(f"Error: {e}")
Common Built-in Exceptions
| Exception | Description |
|---|---|
| FileNotFoundError | File or directory not found |
| PermissionError | Permission denied for file operation |
| IOError | Input/Output operation failed |
| ValueError | Invalid value or type |
| TypeError | Operation on incompatible types |
| ZeroDivisionError | Division by zero |
| IndexError | Index out of range |
| KeyError | Key not found in dictionary |
3.2 Creating Python Packages, Modules and Executable Files
Modules
A module is a Python file containing functions, classes, and variables that can be imported into other programs.
Creating a Module (mymodule.py):
# mymodule.py
def greet(name):
return f"Hello, {name}!"
def add(a, b):
return a + b
PI = 3.14159
class Calculator:
def multiply(self, a, b):
return a * b
Using the Module:
# main.py
import mymodule
print(mymodule.greet("Alice"))
print(mymodule.add(5, 3))
print(mymodule.PI)
calc = mymodule.Calculator()
print(calc.multiply(4, 5))
# Import specific items
from mymodule import greet, PI
print(greet("Bob"))
print(PI)
# Import with alias
import mymodule as mm
print(mm.add(10, 20))
# Import all (not recommended)
from mymodule import *
Packages
A package is a directory containing multiple modules and a special __init__.py file.
Package Structure:
mypackage/
__init__.py
module1.py
module2.py
subpackage/
__init__.py
module3.py
__init__.py file:
# mypackage/__init__.py
from .module1 import function1
from .module2 import function2
__all__ = ['function1', 'function2']
Using Packages:
# Importing from package
from mypackage import module1
from mypackage.module1 import function1
from mypackage.subpackage import module3
# Using the imported functions
result = function1()
module1.another_function()
The __name__ Variable
Used to determine if a file is run directly or imported:
# mymodule.py
def main():
print("Running as main program")
if __name__ == "__main__":
main()
# This code runs only when the file is executed directly
# Not when it's imported as a module
Creating Executable Files
Python scripts can be converted to standalone executables using tools like PyInstaller:
Installing PyInstaller:
pip install pyinstaller
Creating an Executable:
# Basic executable
pyinstaller myscript.py
# Single file executable
pyinstaller --onefile myscript.py
# Without console window (for GUI apps)
pyinstaller --onefile --noconsole myscript.py
# With custom icon
pyinstaller --onefile --icon=myicon.ico myscript.py
Built-in Modules
| Module | Description |
|---|---|
| os | Operating system interface |
| sys | System-specific parameters |
| math | Mathematical functions |
| datetime | Date and time handling |
| json | JSON encoding/decoding |
| random | Random number generation |
| re | Regular expressions |
3.3 Dealing with Syntax Errors, Runtime Errors and Scientific Debugging
Types of Errors
1. Syntax Errors
Errors in the structure of the code that prevent it from running:
# Missing colon
if x > 5
print("x is greater") # SyntaxError
# Incorrect indentation
def my_function():
print("Hello") # IndentationError
# Unmatched parentheses
print("Hello" # SyntaxError
# Invalid syntax
x = 5 + # SyntaxError
Common Syntax Errors:
- Missing colons after if, for, while, def, class statements
- Incorrect indentation
- Unmatched parentheses, brackets, or quotes
- Using reserved keywords as variable names
- Missing commas in lists or function arguments
2. Runtime Errors (Exceptions)
Errors that occur during program execution:
# ZeroDivisionError
result = 10 / 0
# TypeError
result = "5" + 5
# IndexError
my_list = [1, 2, 3]
print(my_list[10])
# KeyError
my_dict = {"name": "John"}
print(my_dict["age"])
# NameError
print(undefined_variable)
# AttributeError
x = 5
x.append(10) # int has no append method
3. Logical Errors
Code runs without errors but produces incorrect results:
# Incorrect logic - should be average, not sum
def calculate_average(numbers):
return sum(numbers) # Missing division by length
# Off-by-one error
for i in range(10): # Should be range(11) to include 10
print(i)
Scientific Debugging Techniques
1. Print Debugging
def calculate_total(items):
total = 0
print(f"Starting calculation with {len(items)} items")
for item in items:
print(f"Processing item: {item}")
total += item
print(f"Running total: {total}")
print(f"Final total: {total}")
return total
2. Using the Python Debugger (pdb)
import pdb
def buggy_function(x, y):
pdb.set_trace() # Debugger stops here
result = x + y
return result
# pdb commands:
# n - next line
# s - step into function
# c - continue execution
# p variable - print variable value
# l - list source code
# q - quit debugger
3. Using breakpoint() (Python 3.7+)
def my_function(data):
breakpoint() # Built-in debugger
processed = process_data(data)
return processed
4. Assertions
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
assert all(isinstance(n, (int, float)) for n in numbers), "All items must be numbers"
return sum(numbers) / len(numbers)
# Assertions help catch bugs early
try:
avg = calculate_average([])
except AssertionError as e:
print(f"Assertion failed: {e}")
5. Logging
import logging
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def process_data(data):
logging.debug(f"Input data: {data}")
logging.info("Starting data processing")
try:
result = data * 2
logging.info(f"Processing successful: {result}")
return result
except Exception as e:
logging.error(f"Processing failed: {e}")
raise
# Logging levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
6. Unit Testing
import unittest
def add(a, b):
return a + b
class TestAddFunction(unittest.TestCase):
def test_positive_numbers(self):
self.assertEqual(add(2, 3), 5)
def test_negative_numbers(self):
self.assertEqual(add(-1, -1), -2)
def test_mixed_numbers(self):
self.assertEqual(add(-1, 1), 0)
if __name__ == '__main__':
unittest.main()
Debugging Best Practices
- Reproduce the bug: Understand the exact conditions that cause the error
- Isolate the problem: Narrow down the code section causing the issue
- Read error messages carefully: Python provides detailed traceback information
- Use version control: Track changes to identify when bugs were introduced
- Write tests: Prevent regression by testing after fixes
- Rubber duck debugging: Explain your code line by line to find issues
- Take breaks: Fresh eyes often spot problems faster
Understanding Tracebacks
Traceback (most recent call last):
File "main.py", line 10, in <module>
result = calculate(5, 0)
File "main.py", line 5, in calculate
return a / b
ZeroDivisionError: division by zero
# Reading traceback:
# 1. Start from the bottom - shows the actual error
# 2. Work upward to trace the call stack
# 3. Line numbers help locate the issue