PCAP study materials
PCAP study resources

The Silent Exception

PCAP · Put your knowledge to work in context.

All case studies

The Silent Exception

Consider this code:

def process(data):
    try:
        result = 10 / data
    except Exception:
        print("Error")
        return None
    finally:
        print("Done")
    return result

print(process(0))
print(process(2))

A developer expects the first call to print Error, then Done, then None, and the second call to print Done then 5.0. However, when run, the output is:

Error
Done
None
Done
5.0

This matches the expectation. But then the developer modifies the code to:

def process(data):
    try:
        result = 10 / data
    except Exception:
        print("Error")
        return None
    finally:
        print("Done")
        return -1
    return result

print(process(0))
print(process(2))

Now the output is:

Error
Done
-1
Done
-1

The developer is confused: why does the second call now return -1 instead of 5.0?

1. Explain why adding `return -1` in the `finally` block changes the return value for both calls. What does this reveal about the behavior of `return` inside `finally`?

Read the worked answer

A return statement inside a finally block overrides any previous return value, including one from the try or except block, and even one from a pending exception. In the modified code, the finally block executes after the try (or except) block and before the function actually returns. When it hits return -1, it immediately returns -1 from the function, discarding any value that was about to be returned. For process(0), the except block returns None, but then finally runs and returns -1, so the function returns -1. For process(2), the try block computes result = 5.0 and then finally runs and returns -1, so the function returns -1. The return result after the try block is never reached because the finally block always executes and its return exits the function. This is a common pitfall: using return in finally can suppress exceptions and override return values, and is generally discouraged.

2. If the `finally` block contained `raise ValueError` instead of `return -1`, what would be the output for `process(0)` and `process(2)`? Assume the rest of the code is the same as the modified version (with `return -1` replaced by `raise ValueError`).

Read the worked answer

If the finally block raises ValueError, that exception will propagate and override any pending exception or return value. For process(0), the except block catches the ZeroDivisionError and prints Error, then returns None. But before returning, finally runs and raises ValueError, so the function raises ValueError instead of returning None. For process(2), the try block computes 5.0 and then finally raises ValueError, so the function raises ValueError instead of returning 5.0. In both cases, the ValueError propagates to the caller, and the print statements around the calls would not execute unless wrapped in try/except. The output would be Error then Done (from the print in finally? Wait, the finally block in the modified version has print("Done") and then return -1. If we replace return -1 with raise ValueError, the finally block still has print("Done") before the raise. So for process(0): except prints Error, then finally prints Done, then raises ValueError. The print(process(0)) call would raise, so nothing else printed. Similarly for process(2): try computes 5.0, then finally prints Done, then raises ValueError. So the output would be Error, Done, then a traceback for the first call, and if the script continues, Done then traceback for the second call. But since the first call raises, the script stops unless caught. So the output is Error and Done before the traceback.