Python Exception Handling

Exception handling is a core mechanism in programming languages for managing unexpected or out-of-normal runtime conditions. Python includes robust tools for catching and resolving exceptions, with the primary try…except syntax family and assertion statements.

The most common structure is try-except-else-finally, with each clause serving a distinct purpose:

  • try: Contains code that will execute under normal program flow
  • except: Executes only when an exception is raised within the try block
  • else: Runs exclusively if the try block completes without any exceptions
  • finally: Always runs, regardless of whether an exception occurred in the try block

Basic Syntax

try:
    # Core execution code
    pass
except ExceptionType:
    # Error handling logic
    pass
else:
    # Optional success-time code
    pass
finally:
    # Mandatory cleanup/teardown code
    pass

Exception Handling in Functions

A key detail of exception handling in Python functions is how return statements interact with the finally block. Below are common behavior scenarios:

Scenario 1: Except block has return, Finally block has return

def test_exception():
    try:
        # Trigger division by zero error
        divide_result = 5.0 / 0.0
        print('Output: I am in try block')
        return 0
    except:
        print('Output: I am in except block')
        return 1
    else:
        print('Output: I am in else block')
        return 2
    finally:
        print('Output: I am in finally block')
        return 3

print(f'test: {test_exception()}')

Output:

I am in except block
I am in finally block
test: 3

Eventhough the except block includes a return statement, the finally block still runs. If finally has its own return, that value will override any prior return values from try or except.

Scenario 2: Except block has return, Finally block has no return

def test_exception():
    try:
        divide_result = 5.0 / 0.0
        print('Output: I am in try block')
        return 0
    except:
        print('Output: I am in except block')
        return 1
    else:
        print('Output: I am in else block')
        return 2
    finally:
        print('Output: I am in finally block')

print(f'test: {test_exception()}')

Output:

I am in except block
I am in finally block
test: 1

Here, the finally block runs after the except return, and since it has no return statemant, the value from the except block is used as the function's return value.

Scenario 3: Try block has return, Finally block has return

def test_exception():
    try:
        # Successful division
        divide_result = 5.0 / 1.0
        print('Output: I am in try block')
        return 0
    except:
        print('Output: I am in except block')
        return 1
    else:
        print('Output: I am in else block')
        return 2
    finally:
        print('Output: I am in finally block')
        return 3

print(f'test: {test_exception()}')

Output:

I am in try block
I am in finally block
test: 3

Even with an early return in the try block, the finally block runs, and its return value takes precedence.

Scenario 4: Try block has return, Finally block has no return

def test_exception():
    try:
        divide_result = 5.0 / 1.0
        print('Output: I am in try block')
        return 0
    except:
        print('Output: I am in except block')
        return 1
    else:
        print('Output: I am in else block')
        return 2
    finally:
        print('Output: I am in finally block')

print(f'test: {test_exception()}')

Output:

I am in try block
I am in finally block
test: 0

The else block is skipped due to the early try return, and the finally block runs, using the try block's return value since finally has no return.

Scenario 5: No exceptions, Try has no return, Finally has no return

def test_exception():
    try:
        divide_result = 5.0 / 1.0
        print('Output: I am in try block')
    except:
        print('Output: I am in except block')
        return 1
    else:
        print('Output: I am in else block')
        return 2
    finally:
        print('Output: I am in finally block')

print(f'test: {test_exception()}')

Output:

I am in try block
I am in else block
I am in finally block
test: 2

Since there are no exceptions and no early return in try, the else block runs, followed by finally, using the else block's return value.

Key Summary Rules

  1. The finally block always executes, regardless of whether an exception occurred
  2. The except block runs only when an exception is raised in the try block
  3. The else block runs only if the try block completes without exceptions and has no early return
  4. If the try block includes an early return, the else block is skipped entirely
  5. If the try block has no early return and no exceptions, the else block executes before the finally block

Practical Application: Grid Grass Spread

This example uses exception handling to handle boundary checks when simulating grass spreading on a grid:

Problem: Given an n x m grid where some cells have grass (g) and others are empty (.), each month grass spreads to all 4 adjacent cells (up, down, left, right). Calculate the grid state after k months.

Input: First line has n and m, next n lines have the grid state, final line has k.

Output: The final grid state after k months.

Sample Input:

4 5
.g...
.....
..g..
.....
2

Sample Output:

gggg.
gggg.
ggggg
.ggg.
# Read grid dimensions
n, m = map(int, input().split())
# Store initial grid state
initial_grid = [list(input().strip()) for _ in range(n)]
# Create a copy to avoid overwriting grass during current iteration
updated_grid = [row.copy() for row in initial_grid]
# Read number of months
months = int(input())

for _ in range(months):
    # Reset updated grid for each month
    updated_grid = [row.copy() for row in initial_grid]
    for i in range(n):
        for j in range(m):
            if initial_grid[i][j] == 'g':
                # Spread grass to adjacent cells, use try-except for boundary checks
                # Up
                if i > 0:
                    updated_grid[i-1][j] = 'g'
                # Down
                try:
                    updated_grid[i+1][j] = 'g'
                except IndexError:
                    pass
                # Left
                if j > 0:
                    updated_grid[i][j-1] = 'g'
                # Right
                try:
                    updated_grid[i][j+1] = 'g'
                except IndexError:
                    pass
    # Update initial grid for next month's iteration
    initial_grid = [row.copy() for row in updated_grid]

# Print final grid
for row in initial_grid:
    print(''.join(row))

Tags: python Exception Handling Error Handling Try Except Finally Grid Simulation

Posted on Mon, 17 Aug 2026 16:29:21 +0000 by Alka-Seltzer