Lecture 2: Directories and Exception Handling

1. Working with Directories

Using the os Module

import os # Get current working directory current_dir = os.getcwd() print(f"Current directory: {current_dir}") # List directory contents print("\nDirectory contents:") for item in os.listdir('.'): print(f"- {item}") # Create a new directory os.mkdir('new_directory') # Create directories recursively os.makedirs('path/to/new/directory', exist_ok=True) # Remove a directory (must be empty) os.rmdir('new_directory') # Remove directory tree (use with caution!) import shutil shutil.rmtree('path/to/remove', ignore_errors=True)

File and Directory Operations

import os import shutil # Check if path exists path = 'example.txt' if os.path.exists(path): print(f"{path} exists!") # Check if path is a file or directory if os.path.isfile(path): print(f"{path} is a file") elif os.path.isdir(path): print(f"{path} is a directory") # Get file size size = os.path.getsize(path) print(f"File size: {size} bytes") # Get file modification time mod_time = os.path.getmtime(path) from datetime import datetime print(f"Last modified: {datetime.fromtimestamp(mod_time)}") # Copy files shutil.copy('source.txt', 'destination.txt') # Move/rename files os.rename('old_name.txt', 'new_name.txt') # Delete a file os.remove('file_to_delete.txt')

2. Walking Directory Trees

import os def list_files(startpath): for root, dirs, files in os.walk(startpath): level = root.replace(startpath, '').count(os.sep) indent = ' ' * 4 * level print(f"{indent}{os.path.basename(root)}/") subindent = ' ' * 4 * (level + 1) for f in files: print(f"{subindent}{f}") # Example usage list_files('.') # Find all .py files in a directory tree def find_py_files(directory): py_files = [] for root, dirs, files in os.walk(directory): for file in files: if file.endswith('.py'): py_files.append(os.path.join(root, file)) return py_files # Count lines of code in all Python files def count_lines(directory): total_lines = 0 for py_file in find_py_files(directory): with open(py_file, 'r', encoding='utf-8') as f: lines = f.readlines() total_lines += len(lines) print(f"{py_file}: {len(lines)} lines") print(f"\nTotal lines of Python code: {total_lines}")

3. Errors vs Exceptions

Key Differences:

  • Errors are problems that a program should not try to handle (e.g., SyntaxError, IndentationError)
  • Exceptions are conditions that a program might want to handle (e.g., FileNotFoundError, ZeroDivisionError)

Common Built-in Exceptions

Exception Description
Exception Base class for all exceptions
ArithmeticError Base class for arithmetic errors
ZeroDivisionError Division or modulo by zero
FileNotFoundError File or directory doesn't exist
PermissionError No permission to access a file
ValueError Incorrect value passed to a function
TypeError Operation on inappropriate type
IndexError Sequence index out of range
KeyError Dictionary key not found
IOError I/O related errors

4. The Exception Hierarchy

# View the built-in exception hierarchy def print_exception_hierarchy(exception_class, indent=0): print(' ' * indent + exception_class.__name__) for subclass in exception_class.__subclasses__(): print_exception_hierarchy(subclass, indent + 4) # Print the hierarchy starting from BaseException print_exception_hierarchy(BaseException)

Key Points:

  • BaseException is the root class for all exceptions
  • Exception is the base class for all built-in, non-system-exiting exceptions
  • Most user-defined exceptions should inherit from Exception
  • System-exiting exceptions like SystemExit and KeyboardInterrupt inherit directly from BaseException

5. Practical Example: Directory Cleanup Utility

import os import shutil from datetime import datetime, timedelta def cleanup_directory(directory, days_old=30, extensions=None, dry_run=False): """ Remove files older than specified days with given extensions. Args: directory (str): Directory to clean up days_old (int): Remove files older than this many days extensions (list): List of file extensions to consider (None means all) dry_run (bool): If True, only show what would be deleted """ if not os.path.exists(directory): print(f"Error: Directory '{directory}' does not exist.") return if not os.path.isdir(directory): print(f"Error: '{directory}' is not a directory.") return cutoff_time = datetime.now() - timedelta(days=days_old) total_size = 0 removed_count = 0 print(f"Cleaning up directory: {directory}") print(f"Removing files older than: {cutoff_time}") if extensions: print(f"File extensions: {', '.join(extensions) if extensions else 'All'}") print("-" * 50) for root, dirs, files in os.walk(directory, topdown=False): for file in files: if extensions and not any(file.lower().endswith(ext.lower()) for ext in extensions): continue file_path = os.path.join(root, file) try: mod_time = datetime.fromtimestamp(os.path.getmtime(file_path)) if mod_time < cutoff_time: file_size = os.path.getsize(file_path) print(f"Removing: {file_path} (Last modified: {mod_time}, Size: {file_size} bytes)") if not dry_run: try: if os.path.isfile(file_path): os.remove(file_path) elif os.path.isdir(file_path): shutil.rmtree(file_path) removed_count += 1 total_size += file_size except Exception as e: print(f" Error removing {file_path}: {e}") except Exception as e: print(f" Error processing {file_path}: {e}") print("-" * 50) print(f"Total files removed: {removed_count}") print(f"Total space freed: {total_size / (1024*1024):.2f} MB") # Example usage if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description='Clean up old files in a directory.') parser.add_argument('directory', help='Directory to clean up') parser.add_argument('--days', type=int, default=30, help='Remove files older than this many days (default: 30)') parser.add_argument('--ext', nargs='+', help='File extensions to include (e.g., .tmp .log)') parser.add_argument('--dry-run', action='store_true', help='Show what would be deleted without actually deleting') args = parser.parse_args() cleanup_directory( args.directory, days_old=args.days, extensions=args.ext, dry_run=args.dry_run )