Web Analytics

Finally & Else

Intermediate ~25 min read

The finally clause guarantees code runs no matter what - whether an exception occurs, the code returns early, or breaks from a loop. The else clause runs only when no exception occurred. Together, they complete Python's exception handling toolkit, enabling robust resource management and clean code separation!

Finally Block Basics

The finally block always executes - when an exception occurs, when no exception occurs, even when the code uses return, break, or continue. This makes it perfect for cleanup code that must run no matter what happens.

Output
Click Run to execute your code
When Finally Runs:
- After try completes normally
- After except handles an exception
- Before return completes
- Before break or continue in loops
- Before an exception propagates up

Key insight: Finally runs BEFORE control leaves the try/except block!

Finally with Return Statements

When a function returns inside a try block, finally still runs - but there's a catch. If finally also has a return statement, it overrides the try's return! This is usually a bug, so avoid return statements in finally blocks.

Output
Click Run to execute your code
Avoid These in Finally!
- return - Overrides the try block's return value
- raise - Replaces the original exception
- break/continue - Overrides loop control

Finally should ONLY contain cleanup code - closing files, releasing locks, etc.

The Else Clause

The else clause runs only when no exception occurred in the try block. This lets you separate "code that might fail" from "code to run on success". It keeps your try block minimal and makes the code's intent clearer.

Output
Click Run to execute your code
Execution Order:
1. try - Runs first
2. except - Only if exception occurred
3. else - Only if NO exception occurred
4. finally - Always runs last

Why use else? Keep try blocks minimal - only wrap code that might fail!

Resource Management Patterns

The most common use of finally is resource cleanup - closing files, disconnecting from databases, releasing locks. But Python's with statement (context managers) is usually cleaner. Still, understanding the finally pattern helps when with isn't available.

Output
Click Run to execute your code
Prefer Context Managers:
with open(file) as f: - File automatically closed
with lock: - Lock automatically released
with connection: - Connection automatically closed

Use try/finally only when context managers aren't available or appropriate.

Common Mistakes

1. Returning in finally

# Wrong - return in finally overrides try's return!
def get_data():
    try:
        return fetch_from_server()
    finally:
        return None  # This ALWAYS returns None!

# Correct - only cleanup in finally
def get_data():
    connection = None
    try:
        connection = connect()
        return connection.fetch()
    finally:
        if connection:
            connection.close()  # Just cleanup, no return

2. Raising in finally (suppresses original)

# Wrong - new exception replaces original!
def process():
    try:
        raise ValueError("Original error")
    finally:
        raise RuntimeError("Cleanup failed")  # Original lost!

# Correct - log cleanup errors, don't raise
def process():
    try:
        raise ValueError("Original error")
    finally:
        try:
            cleanup()
        except Exception as e:
            logging.error(f"Cleanup failed: {e}")
            # Don't raise - let original propagate

3. Confusing else with finally

# Wrong - using else for cleanup
try:
    data = load_data()
except FileNotFoundError:
    data = default_data()
else:
    file.close()  # Wrong place! What if exception occurred?

# Correct - else for success-only code, finally for cleanup
try:
    data = load_data()
except FileNotFoundError:
    data = default_data()
else:
    process(data)  # Only runs on success
finally:
    file.close()  # Always runs

4. Too much code in try block

# Wrong - everything in try
try:
    f = open(filename)
    data = f.read()
    parsed = json.loads(data)
    result = process(parsed)
    save(result)
except Exception:
    print("Something failed")  # Which part?

# Correct - minimal try, use else for success path
try:
    f = open(filename)
except FileNotFoundError:
    print("File not found")
    f = None
else:
    try:
        data = f.read()
        parsed = json.loads(data)
    except json.JSONDecodeError:
        print("Invalid JSON")
    else:
        result = process(parsed)
        save(result)
finally:
    if f:
        f.close()

5. Not using context managers when available

# Verbose - manual try/finally
f = None
try:
    f = open(filename)
    data = f.read()
finally:
    if f:
        f.close()

# Better - context manager handles cleanup
with open(filename) as f:
    data = f.read()
# Automatically closed, even if exception occurs!

# For multiple resources
with open('in.txt') as infile, open('out.txt', 'w') as outfile:
    outfile.write(infile.read())

Exercise: Safe Database Query

Task: Create a function that safely queries a database with proper cleanup.

Requirements:

  • Connect to the database in try block
  • Use else for processing the results
  • Always disconnect in finally, even if errors occur
  • Return the results or an error message
Output
Click Run to execute your code
Show Solution
class Database:
    """Simulated database for exercise."""
    def __init__(self, name):
        self.name = name
        self.connected = False

    def connect(self):
        self.connected = True
        print(f"Connected to {self.name}")

    def disconnect(self):
        if self.connected:
            self.connected = False
            print(f"Disconnected from {self.name}")

    def query(self, sql):
        if not self.connected:
            raise RuntimeError("Not connected!")
        if "error" in sql.lower():
            raise ValueError("Query syntax error")
        return [{"id": 1}, {"id": 2}]


def safe_query(db, sql):
    """
    Safely query database with proper cleanup.
    Returns (success, result_or_error).
    """
    try:
        db.connect()
    except Exception as e:
        return (False, f"Connection failed: {e}")
    else:
        # Only runs if connect succeeded
        try:
            results = db.query(sql)
            return (True, results)
        except ValueError as e:
            return (False, f"Query failed: {e}")
    finally:
        # Always disconnect
        db.disconnect()


# Test the function
db = Database("production")

print("=== Test 1: Successful query ===")
success, result = safe_query(db, "SELECT * FROM users")
print(f"Success: {success}, Result: {result}\n")

print("=== Test 2: Failed query ===")
success, result = safe_query(db, "SELECT error FROM bad")
print(f"Success: {success}, Result: {result}\n")

print("=== Verify cleanup ===")
print(f"Database connected: {db.connected}")  # Should be False

Summary

  • finally: Always runs - perfect for cleanup code
  • else: Runs only when no exception occurred
  • Order: try → except (if error) OR else (if no error) → finally
  • finally with return: Runs before return, but avoid returning in finally
  • Don't in finally: return, raise, break, continue
  • Use else to: Keep try blocks minimal, separate success code
  • Resource management: Finally ensures cleanup happens
  • Prefer context managers: with is cleaner than try/finally

What's Next?

Now you know how to catch and clean up after exceptions. But what if you need to signal an error yourself? Next, we'll learn about raise to create and throw your own exceptions, and how to create custom exception classes for your specific error conditions!