Using finally in Python Exception Handling
Table of Contents
1. [Introduction](#introduction) 2. [Basic Syntax and Structure](#basic-syntax-and-structure) 3. [Howfinally Works](#how-finally-works)
4. [Use Cases for finally](#use-cases-for-finally)
5. [Resource Management](#resource-management)
6. [Cleanup Operations](#cleanup-operations)
7. [Advanced Scenarios](#advanced-scenarios)
8. [Best Practices](#best-practices)
9. [Common Pitfalls](#common-pitfalls)
10. [Examples and Code Demonstrations](#examples-and-code-demonstrations)Introduction
The finally block in Python is a crucial component of exception handling that ensures certain code is executed regardless of whether an exception occurs or not. It provides a reliable mechanism for cleanup operations, resource management, and any code that must run unconditionally.
The finally block is part of Python's try-except-finally statement structure and serves as a guarantee that specific code will execute, making it invaluable for maintaining program integrity and proper resource management.
Basic Syntax and Structure
Standard Try-Except-Finally Structure
`python
try:
# Code that might raise an exception
risky_operation()
except ExceptionType:
# Exception handling code
handle_exception()
finally:
# Code that always executes
cleanup_operations()
`
Minimal Structure with Finally
`python
try:
# Code that might raise an exception
some_operation()
finally:
# Cleanup code
cleanup()
`
How finally Works
The finally block operates under specific rules that determine when and how it executes:
| Scenario | Finally Block Execution | Notes | |----------|------------------------|-------| | No exception occurs | Executes after try block | Normal flow completion | | Exception caught by except | Executes after except block | After exception handling | | Exception not caught | Executes before exception propagates | Exception continues after finally | | Return statement in try | Executes before return | Can modify return value in some cases | | Break/Continue in loop | Executes before break/continue | Loop control happens after finally | | System exit called | May or may not execute | Depends on exit method |
Execution Flow Examples
`python
def demonstrate_finally_flow():
"""Demonstrates the execution flow with finally block"""
print("Example 1: Normal execution")
try:
print("In try block")
result = 10 / 2
print(f"Result: {result}")
except ZeroDivisionError:
print("In except block")
finally:
print("In finally block")
print("After try-except-finally")
print("-" * 40)
print("Example 2: Exception occurs")
try:
print("In try block")
result = 10 / 0 # This will raise ZeroDivisionError
print(f"Result: {result}") # This won't execute
except ZeroDivisionError:
print("In except block - handling division by zero")
finally:
print("In finally block")
print("After try-except-finally")
print("-" * 40)
print("Example 3: Unhandled exception")
try:
try:
print("In inner try block")
raise ValueError("Custom error")
except ZeroDivisionError: # Wrong exception type
print("In except block")
finally:
print("In finally block - executes even with unhandled exception")
except ValueError:
print("Caught ValueError in outer try-except")
Execute the demonstration
demonstrate_finally_flow()`Output:
`
Example 1: Normal execution
In try block
Result: 5.0
In finally block
After try-except-finally
----------------------------------------
Example 2: Exception occurs
In try block
In except block - handling division by zero
In finally block
After try-except-finally
----------------------------------------
Example 3: Unhandled exception
In inner try block
In finally block - executes even with unhandled exception
Caught ValueError in outer try-except
`
Use Cases for finally
Resource Management
The most common use case for finally blocks is ensuring proper resource cleanup:
`python
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connected = False
def connect(self):
print(f"Connecting to database: {self.connection_string}")
self.connected = True
return self
def execute_query(self, query):
if not self.connected:
raise RuntimeError("Not connected to database")
print(f"Executing query: {query}")
if "DROP" in query.upper():
raise RuntimeError("Dangerous operation detected")
return f"Results for: {query}"
def disconnect(self):
if self.connected:
print("Disconnecting from database")
self.connected = False
def database_operation_with_finally(): """Demonstrates resource management using finally""" db = None try: db = DatabaseConnection("postgresql://localhost:5432/mydb").connect() result = db.execute_query("SELECT * FROM users") print(f"Query result: {result}") return result except RuntimeError as e: print(f"Database error: {e}") return None finally: # Ensure database connection is closed regardless of what happens if db and db.connected: db.disconnect() print("Database cleanup completed")
Test with successful operation
print("Test 1: Successful operation") database_operation_with_finally() print()Test with exception
print("Test 2: Operation with exception") def test_with_dangerous_query(): db = None try: db = DatabaseConnection("postgresql://localhost:5432/mydb").connect() result = db.execute_query("DROP TABLE users") # This will raise an exception print(f"Query result: {result}") return result except RuntimeError as e: print(f"Database error: {e}") return None finally: if db and db.connected: db.disconnect() print("Database cleanup completed")test_with_dangerous_query()
`
File Operations
`python
def file_operations_with_finally():
"""Demonstrates file handling with finally block"""
file_handle = None
try:
print("Opening file for writing")
file_handle = open("test_file.txt", "w")
print("Writing data to file")
file_handle.write("Line 1: Important data\n")
file_handle.write("Line 2: More important data\n")
# Simulate an error condition
if True: # Change to False to test normal flow
raise IOError("Simulated write error")
file_handle.write("Line 3: This won't be written due to error\n")
except IOError as e:
print(f"File operation error: {e}")
finally:
# Ensure file is closed regardless of success or failure
if file_handle and not file_handle.closed:
print("Closing file in finally block")
file_handle.close()
else:
print("File was already closed or never opened")
file_operations_with_finally()
Verify file contents
try: with open("test_file.txt", "r") as f: print("File contents:") print(f.read()) except FileNotFoundError: print("File was not created")`Resource Management
Comparison with Context Managers
While finally blocks are useful for cleanup, Python's context managers (with statement) often provide a cleaner solution:
| Approach | Pros | Cons | Best Use Case |
|----------|------|------|---------------|
| finally block | Explicit control, works with any resource | More verbose, easy to forget | Complex cleanup logic |
| Context manager | Automatic cleanup, cleaner syntax | Requires context manager protocol | Standard resource management |
| try-finally only | Simple, no exception handling | No exception handling | Cleanup without error handling |
`python
import time
import threading
class ResourceManager: def __init__(self, resource_name): self.resource_name = resource_name self.acquired = False self.lock = threading.Lock() def acquire(self): print(f"Acquiring resource: {self.resource_name}") with self.lock: if self.acquired: raise RuntimeError(f"Resource {self.resource_name} already acquired") self.acquired = True time.sleep(0.1) # Simulate acquisition time print(f"Resource {self.resource_name} acquired successfully") def release(self): print(f"Releasing resource: {self.resource_name}") with self.lock: if not self.acquired: print(f"Warning: Resource {self.resource_name} was not acquired") return self.acquired = False time.sleep(0.1) # Simulate release time print(f"Resource {self.resource_name} released successfully") # Context manager protocol def __enter__(self): self.acquire() return self def __exit__(self, exc_type, exc_val, exc_tb): self.release() return False # Don't suppress exceptions
Using finally block
def use_resource_with_finally(): resource = ResourceManager("Database Connection") try: resource.acquire() print("Performing operations with resource") # Simulate some work time.sleep(0.2) # Simulate an error raise ValueError("Something went wrong during operation") except ValueError as e: print(f"Operation failed: {e}") finally: # Ensure resource is released resource.release()Using context manager
def use_resource_with_context_manager(): try: with ResourceManager("Database Connection") as resource: print("Performing operations with resource") # Simulate some work time.sleep(0.2) # Simulate an error raise ValueError("Something went wrong during operation") except ValueError as e: print(f"Operation failed: {e}")print("Using finally block:")
use_resource_with_finally()
print("\nUsing context manager:")
use_resource_with_context_manager()
`
Cleanup Operations
Temporary File Cleanup
`python
import os
import tempfile
import shutil
def process_data_with_temp_files(): """Demonstrates cleanup of temporary files using finally""" temp_dir = None temp_files = [] try: # Create temporary directory temp_dir = tempfile.mkdtemp(prefix="data_processing_") print(f"Created temporary directory: {temp_dir}") # Create temporary files for processing for i in range(3): temp_file = os.path.join(temp_dir, f"temp_data_{i}.txt") temp_files.append(temp_file) with open(temp_file, 'w') as f: f.write(f"Temporary data for file {i}\n") f.write(f"Processing timestamp: {time.time()}\n") print(f"Created temporary file: {temp_file}") # Simulate data processing print("Processing data...") time.sleep(1) # Simulate an error during processing if len(temp_files) > 2: raise RuntimeError("Processing failed due to too many files") print("Data processing completed successfully") except RuntimeError as e: print(f"Processing error: {e}") finally: # Cleanup temporary files and directory print("Starting cleanup operations...") # Remove individual files for temp_file in temp_files: try: if os.path.exists(temp_file): os.remove(temp_file) print(f"Removed temporary file: {temp_file}") except OSError as e: print(f"Error removing file {temp_file}: {e}") # Remove temporary directory if temp_dir and os.path.exists(temp_dir): try: shutil.rmtree(temp_dir) print(f"Removed temporary directory: {temp_dir}") except OSError as e: print(f"Error removing directory {temp_dir}: {e}") print("Cleanup operations completed")
process_data_with_temp_files()
`
Network Connection Cleanup
`python
import socket
import time
class NetworkClient: def __init__(self, host, port): self.host = host self.port = port self.socket = None self.connected = False def connect(self): print(f"Attempting to connect to {self.host}:{self.port}") self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(5) # 5 second timeout try: self.socket.connect((self.host, self.port)) self.connected = True print("Connection established") except (socket.error, socket.timeout) as e: print(f"Connection failed: {e}") self.socket.close() self.socket = None raise def send_data(self, data): if not self.connected or not self.socket: raise RuntimeError("Not connected") try: self.socket.send(data.encode('utf-8')) print(f"Sent data: {data}") except socket.error as e: print(f"Send failed: {e}") raise def disconnect(self): if self.socket: print("Closing network connection") self.socket.close() self.socket = None self.connected = False
def network_operation_with_cleanup(): """Demonstrates network cleanup using finally""" client = NetworkClient("httpbin.org", 80) try: client.connect() # Send HTTP request http_request = "GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n" client.send_data(http_request) # Simulate processing response print("Processing response...") time.sleep(1) # Simulate an error during processing raise RuntimeError("Simulated processing error") except (socket.error, RuntimeError) as e: print(f"Network operation failed: {e}") finally: # Ensure connection is properly closed client.disconnect() print("Network cleanup completed")
Note: This example may not work in all environments due to network restrictions
network_operation_with_cleanup()
`Advanced Scenarios
Finally with Return Statements
The finally block executes even when return statements are present in try or except blocks:
`python
def finally_with_return_examples():
"""Demonstrates finally block behavior with return statements"""
def example_1():
try:
print("In try block")
return "return from try"
finally:
print("In finally block")
# This doesn't change the return value
print("Finally block completed")
def example_2():
try:
print("In try block")
return "return from try"
finally:
print("In finally block")
return "return from finally" # This overrides the try return
def example_3():
try:
print("In try block")
raise ValueError("Test exception")
except ValueError:
print("In except block")
return "return from except"
finally:
print("In finally block")
# The except return is still used
def example_4():
result = []
try:
print("In try block")
result.append("try")
return result
finally:
print("In finally block")
result.append("finally") # This modifies the returned object
print("Example 1 - Finally doesn't override return:")
result1 = example_1()
print(f"Returned: {result1}")
print()
print("Example 2 - Finally return overrides try return:")
result2 = example_2()
print(f"Returned: {result2}")
print()
print("Example 3 - Finally doesn't override except return:")
result3 = example_3()
print(f"Returned: {result3}")
print()
print("Example 4 - Finally can modify returned object:")
result4 = example_4()
print(f"Returned: {result4}")
finally_with_return_examples()
`
Nested Try-Finally Blocks
`python
def nested_finally_example():
"""Demonstrates nested try-finally blocks"""
print("Starting nested finally example")
try:
print("Outer try block - start")
try:
print("Inner try block - start")
# Simulate some operation
data = [1, 2, 3, 4, 5]
try:
print("Innermost try block")
# This will raise an exception
result = data[10] # IndexError
print(f"Result: {result}")
finally:
print("Innermost finally block")
except IndexError as e:
print(f"Caught IndexError in inner except: {e}")
# Re-raise as a different exception
raise ValueError("Converted from IndexError") from e
finally:
print("Inner finally block")
print("Outer try block - end")
except ValueError as e:
print(f"Caught ValueError in outer except: {e}")
finally:
print("Outer finally block")
print("After all try-finally blocks")
nested_finally_example()
`
Finally in Loops
`python
def finally_in_loops():
"""Demonstrates finally block behavior in loops"""
print("Example 1: Finally with break")
for i in range(5):
try:
print(f"Processing item {i}")
if i == 2:
print("Breaking from loop")
break
finally:
print(f"Finally block for item {i}")
print("After loop with break\n")
print("Example 2: Finally with continue")
for i in range(5):
try:
if i % 2 == 0:
print(f"Skipping even number {i}")
continue
print(f"Processing odd number {i}")
finally:
print(f"Finally block for item {i}")
print("After loop with continue\n")
print("Example 3: Finally with exception in loop")
for i in range(3):
try:
print(f"Processing item {i}")
if i == 1:
raise ValueError(f"Error at item {i}")
except ValueError as e:
print(f"Caught exception: {e}")
finally:
print(f"Finally block for item {i}")
print("After loop with exception")
finally_in_loops()
`
Best Practices
Resource Management Best Practices
`python
import logging
from contextlib import contextmanager
Configure logging
logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__)class BestPracticesDemo: """Demonstrates best practices for using finally blocks""" @staticmethod def practice_1_minimal_finally(): """Keep finally blocks minimal and focused""" resource = None try: resource = acquire_resource() process_data(resource) except Exception as e: logger.error(f"Processing failed: {e}") raise finally: # Only cleanup code in finally if resource: release_resource(resource) @staticmethod def practice_2_exception_handling_in_finally(): """Handle exceptions in finally blocks properly""" resources = [] try: for i in range(3): resource = acquire_resource(f"resource_{i}") resources.append(resource) # Process resources process_multiple_resources(resources) except Exception as e: logger.error(f"Main processing failed: {e}") finally: # Clean up all resources, handling individual failures for resource in resources: try: release_resource(resource) except Exception as cleanup_error: logger.error(f"Cleanup failed for {resource}: {cleanup_error}") # Don't re-raise cleanup exceptions @staticmethod def practice_3_logging_in_finally(): """Proper logging in finally blocks""" operation_id = "OP_001" start_time = time.time() try: logger.info(f"Starting operation {operation_id}") perform_operation() logger.info(f"Operation {operation_id} completed successfully") except Exception as e: logger.error(f"Operation {operation_id} failed: {e}") raise finally: end_time = time.time() duration = end_time - start_time logger.info(f"Operation {operation_id} finished in {duration:.2f} seconds") @staticmethod def practice_4_combining_with_context_managers(): """Combining finally with context managers""" temp_files = [] try: # Use context manager for primary resource with open("main_file.txt", "w") as main_file: main_file.write("Main processing data\n") # Create additional resources that need manual cleanup for i in range(2): temp_file = f"temp_{i}.txt" temp_files.append(temp_file) with open(temp_file, "w") as tf: tf.write(f"Temporary data {i}\n") # Main processing here process_files(["main_file.txt"] + temp_files) except Exception as e: logger.error(f"File processing failed: {e}") finally: # Clean up temporary files for temp_file in temp_files: try: if os.path.exists(temp_file): os.remove(temp_file) logger.info(f"Removed temporary file: {temp_file}") except OSError as e: logger.error(f"Failed to remove {temp_file}: {e}")
Helper functions for demonstration
def acquire_resource(name="default_resource"): logger.info(f"Acquiring resource: {name}") return f"Resource_{name}_{int(time.time())}"def release_resource(resource): logger.info(f"Releasing resource: {resource}") # Simulate potential cleanup failure if "resource_1" in resource.lower(): raise RuntimeError(f"Failed to release {resource}")
def process_data(resource): logger.info(f"Processing data with {resource}")
def process_multiple_resources(resources): logger.info(f"Processing {len(resources)} resources")
def perform_operation(): logger.info("Performing main operation") time.sleep(0.1)
def process_files(file_list): logger.info(f"Processing files: {file_list}")
Demonstrate best practices
demo = BestPracticesDemo()print("Best Practice 1: Minimal finally block") try: demo.practice_1_minimal_finally() except: pass print()
print("Best Practice 2: Exception handling in finally") demo.practice_2_exception_handling_in_finally() print()
print("Best Practice 3: Logging in finally")
try:
demo.practice_3_logging_in_finally()
except:
pass
print()
`
Common Pitfalls
Pitfall Analysis Table
| Pitfall | Description | Problem | Solution | |---------|-------------|---------|----------| | Suppressing Exceptions | Finally block raises exception | Original exception is lost | Handle finally exceptions separately | | Resource Leaks | Not checking resource state | Resources remain open | Always check if resource needs cleanup | | Complex Logic | Too much logic in finally | Hard to debug, potential errors | Keep finally blocks simple | | Return Override | Finally return overrides try/except | Unexpected return values | Avoid returns in finally | | Performance Impact | Heavy operations in finally | Slow cleanup affects performance | Minimize finally block work |
Pitfall Examples and Solutions
`python
def demonstrate_pitfalls():
"""Demonstrates common pitfalls and their solutions"""
print("PITFALL 1: Exception in finally block suppresses original exception")
print("Bad example:")
def bad_finally_with_exception():
try:
raise ValueError("Original exception")
finally:
raise RuntimeError("Finally exception") # This suppresses ValueError
try:
bad_finally_with_exception()
except Exception as e:
print(f"Caught: {type(e).__name__}: {e}")
print("The original ValueError was lost!")
print("\nGood example:")
def good_finally_with_exception():
cleanup_error = None
try:
raise ValueError("Original exception")
finally:
try:
# Risky cleanup operation
raise RuntimeError("Cleanup failed")
except Exception as e:
cleanup_error = e
print(f"Cleanup error (logged but not raised): {e}")
# Handle cleanup error after main exception is processed
if cleanup_error:
print(f"Note: Cleanup had issues: {cleanup_error}")
try:
good_finally_with_exception()
except Exception as e:
print(f"Caught original exception: {type(e).__name__}: {e}")
print("\n" + "="*60)
print("PITFALL 2: Not checking resource state before cleanup")
print("Bad example:")
class BadResourceManager:
def __init__(self):
self.resource = None
def cleanup(self):
self.resource.close() # Will fail if resource is None
def bad_resource_cleanup():
manager = BadResourceManager()
try:
# Simulate failure before resource is acquired
raise ConnectionError("Failed to acquire resource")
finally:
manager.cleanup() # This will raise AttributeError
try:
bad_resource_cleanup()
except Exception as e:
print(f"Cleanup failed: {type(e).__name__}: {e}")
print("\nGood example:")
class GoodResourceManager:
def __init__(self):
self.resource = None
def cleanup(self):
if self.resource and hasattr(self.resource, 'close'):
try:
self.resource.close()
print("Resource cleaned up successfully")
except Exception as e:
print(f"Cleanup error: {e}")
else:
print("No resource to clean up")
def good_resource_cleanup():
manager = GoodResourceManager()
try:
raise ConnectionError("Failed to acquire resource")
finally:
manager.cleanup()
try:
good_resource_cleanup()
except Exception as e:
print(f"Main operation failed: {type(e).__name__}: {e}")
print("\n" + "="*60)
print("PITFALL 3: Complex logic in finally block")
print("Bad example - complex finally:")
def bad_complex_finally():
data_processed = 0
try:
# Main processing
for i in range(5):
if i == 3:
raise ValueError("Processing error")
data_processed += 1
finally:
# Too much logic in finally block
print(f"Processed {data_processed} items")
if data_processed < 5:
print("Processing was incomplete")
# Trying to fix the problem in finally (bad practice)
try:
for i in range(data_processed, 5):
print(f"Attempting to process item {i}")
# This could raise more exceptions
if i == 4:
raise RuntimeError("Recovery failed")
except Exception as e:
print(f"Recovery error: {e}")
print("Cleanup completed")
try:
bad_complex_finally()
except Exception as e:
print(f"Main exception: {type(e).__name__}: {e}")
print("\nGood example - simple finally:")
def good_simple_finally():
data_processed = 0
processing_complete = False
try:
for i in range(5):
if i == 3:
raise ValueError("Processing error")
data_processed += 1
processing_complete = True
except ValueError as e:
print(f"Processing failed at item {data_processed}: {e}")
finally:
# Simple cleanup only
print(f"Cleanup: Processed {data_processed} items")
print("Resources cleaned up")
# Handle incomplete processing outside finally
if not processing_complete:
print("Note: Processing was incomplete - manual intervention may be required")
good_simple_finally()
demonstrate_pitfalls()
`
Examples and Code Demonstrations
Comprehensive Example: Log File Processor
`python
import os
import gzip
import json
import time
from datetime import datetime
from typing import List, Dict, Any
class LogFileProcessor: """Comprehensive example demonstrating finally blocks in a real-world scenario""" def __init__(self, input_dir: str, output_dir: str, temp_dir: str): self.input_dir = input_dir self.output_dir = output_dir self.temp_dir = temp_dir self.temp_files = [] self.open_files = [] self.processing_stats = { 'files_processed': 0, 'total_lines': 0, 'errors': 0, 'start_time': None, 'end_time': None } def process_log_files(self, file_pattern: str = "*.log") -> Dict[str, Any]: """Process log files with comprehensive cleanup""" self.processing_stats['start_time'] = datetime.now() try: # Create necessary directories self._ensure_directories() # Get list of files to process log_files = self._get_log_files(file_pattern) if not log_files: raise ValueError(f"No log files found matching pattern: {file_pattern}") print(f"Found {len(log_files)} log files to process") # Process each file for log_file in log_files: self._process_single_file(log_file) # Generate summary report summary = self._generate_summary() print("Log processing completed successfully") return summary except Exception as e: print(f"Log processing failed: {e}") self.processing_stats['errors'] += 1 raise finally: # Comprehensive cleanup self._cleanup_resources() self.processing_stats['end_time'] = datetime.now() self._print_final_stats() def _ensure_directories(self): """Create necessary directories""" for directory in [self.output_dir, self.temp_dir]: if not os.path.exists(directory): os.makedirs(directory) print(f"Created directory: {directory}") def _get_log_files(self, pattern: str) -> List[str]: """Get list of log files to process""" # Simulate finding log files return [f"app_{i}.log" for i in range(1, 4)] def _process_single_file(self, log_file: str): """Process a single log file""" temp_file = None input_file = None output_file = None try: print(f"Processing file: {log_file}") # Create temporary file for processing temp_filename = f"temp_{log_file}_{int(time.time())}.tmp" temp_file = os.path.join(self.temp_dir, temp_filename) self.temp_files.append(temp_file) # Simulate file processing with open(temp_file, 'w') as tf: tf.write(f"Processed data from {log_file}\n") tf.write(f"Processing time: {datetime.now()}\n") tf.write(f"Lines processed: 1000\n") # Simulated self.open_files.append(tf) # Update statistics self.processing_stats['files_processed'] += 1 self.processing_stats['total_lines'] += 1000 # Simulate potential processing error if "app_2.log" in log_file: raise IOError(f"Simulated processing error for {log_file}") # Move processed file to output directory output_path = os.path.join(self.output_dir, f"processed_{log_file}") os.rename(temp_file, output_path) # Remove from temp_files list since it's been moved self.temp_files.remove(temp_file) print(f"Successfully processed: {log_file}") except Exception as e: print(f"Error processing {log_file}: {e}") self.processing_stats['errors'] += 1 # Re-raise to be handled by calling method raise finally: # File-specific cleanup if temp_file in self.temp_files: try: if os.path.exists(temp_file): os.remove(temp_file) self.temp_files.remove(temp_file) print(f"Cleaned up temporary file: {temp_file}") except OSError as e: print(f"Warning: Could not clean up {temp_file}: {e}") def _generate_summary(self) -> Dict[str, Any]: """Generate processing summary""" return { 'files_processed': self.processing_stats['files_processed'], 'total_lines': self.processing_stats['total_lines'], 'errors': self.processing_stats['errors'], 'success_rate': (self.processing_stats['files_processed'] / (self.processing_stats['files_processed'] + self.processing_stats['errors']) * 100 if (self.processing_stats['files_processed'] + self.processing_stats['errors']) > 0 else 0) } def _cleanup_resources(self): """Comprehensive resource cleanup""" print("Starting comprehensive cleanup...") # Close any open files for file_handle in self.open_files: try: if not file_handle.closed: file_handle.close() print(f"Closed file handle: {file_handle.name}") except Exception as e: print(f"Error closing file handle: {e}") # Remove temporary files for temp_file in self.temp_files[:]: # Copy list to avoid modification during iteration try: if os.path.exists(temp_file): os.remove(temp_file) print(f"Removed temporary file: {temp_file}") self.temp_files.remove(temp_file) except OSError as e: print(f"Warning: Could not remove temporary file {temp_file}: {e}") # Remove temporary directory if empty try: if os.path.exists(self.temp_dir) and not os.listdir(self.temp_dir): os.rmdir(self.temp_dir) print(f"Removed empty temporary directory: {self.temp_dir}") except OSError as e: print(f"Could not remove temporary directory: {e}") print("Cleanup completed") def _print_final_stats(self): """Print final processing statistics""" if self.processing_stats['start_time'] and self.processing_stats['end_time']: duration = self.processing_stats['end_time'] - self.processing_stats['start_time'] print(f"\nProcessing Statistics:") print(f"- Duration: {duration}") print(f"- Files processed: {self.processing_stats['files_processed']}") print(f"- Total lines: {self.processing_stats['total_lines']}") print(f"- Errors: {self.processing_stats['errors']}")
Demonstrate the comprehensive example
def run_log_processor_demo(): """Run the log file processor demonstration""" # Setup directories input_dir = "logs_input" output_dir = "logs_output" temp_dir = "logs_temp" processor = LogFileProcessor(input_dir, output_dir, temp_dir) try: summary = processor.process_log_files("*.log") print(f"\nProcessing Summary: {summary}") except Exception as e: print(f"Processing failed with error: {e}") print("\nDemo completed - check cleanup was performed properly")Run the demonstration
run_log_processor_demo()`This comprehensive guide covers all aspects of using finally blocks in Python exception handling, from basic syntax to advanced scenarios and best practices. The finally block is an essential tool for ensuring proper resource cleanup and maintaining program integrity, regardless of whether exceptions occur during execution.