circuit

Caveats of using return with try/except in Python

Behaviour of return with try/except in Python




Behaviour of return with try/except in Python

Note: For non-members, this article is available at https://dineshkumarkb.com/tech/caveats-of-using-return-with-try-except-in-python/

User code can raise built-in exceptions. Python defines try/except to handle exceptions and proceed with the further execution of program without interruption.

Let’s quickly get to an example of a basic try/except clause

try/except statements

Assuming the file is unavailable, executing the below code will give the output as shown below.

try:
    f = open("testfile.txt")
    ...

except FileNotFoundError as e:
    print(f" Error while reading file {e} ")

Output:
Error while reading file [Errno 2] No such file or directory: 'testfile.txt'

In practical use cases such as connecting to a db or opening a file object, we may need to perform teardown operations such db closure/file closure irrespective of the block getting executed. So finally is one such block which can be reserved for these operations as it gets executed always. Let’s looks at an example.

try/except/finally statements

try:
    f = open("testfile.txt")
except FileNotFoundError as e:
    print(f" Error while reading file {e} ")
finally:
    print(" Closing the file ")
    f.close()

So what could possibly go wrong here? Why should we be cautious?

Well, one could easily put foot in their mouth when they use return statements with try/except/finally in Python. Let’s carefully take one step at a time to understand the usage of return statements during exception handling.

1. Usage of return with try/except

def test_func():
    try:
        x = 10
        return x
    except Exception as e:
        x = 20
        return x
    finally:
        x = 30
        return x

print(test_func())

Output:
30

If you think the output of the above code is 10, I am afraid you are wrong. It’s pretty normal to make that assumption because we tend to think that the moment there is a return statement in a function, then it returns(exits) from the function.Well, that may not be true in this case.

From the docs,

  • If a finallyclause is present, the finally clause will execute as the last task before the try statement completes. The finally clause runs whether or not the try statement produces an exception.

  • If the try statement reaches a break, continue or return statement, the finally clause will execute just prior to the break, continue or return statement’s execution.

  • If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement, not the value from the try/except clause’s return statement.

So as you guessed it right, the output of the above code will be 30.

Now, what happens if an exception is raised in the aforementioned code.

2. Usage of return with exceptions

def test_func():
    try:
        x = 10
        raise Exception
    except Exception as e:
        print(f" Raising exception ")
        x = 20
        return x
    finally:
        x = 30
        return x

print(test_func())


Output:
Raising exception
30

So, again the output value of x will be 30. We should remember the fact that a finally statement gets executed ALWAYS.

To have a clearer idea of the execution flow, let’s add print statements in every block.

def test_func():
    try:
        x = 10
        print(f" Inside try block ")
        return x
    except Exception as e:
        x = 20
        print(f" Inside except block ")
        return x
    finally:
        x = 30
        print(f" Inside finally block ")
        return x

print(test_func())


Output:
Inside try block
Inside finally block
30

This would have given an idea on the execution flow.Now that we have a good understanding of how try/except/finally works with return statements, let’s try to squeeze in another clause.

An else clause can be added along with try/except and the else clause will get executed if the try block does not raise an exception.

3. Usage of return with try/else/finally

def test_func():
    try:
        x = 10
        print(f" Inside try block ")
        return x
    except Exception as e:
        x = 20
        print(f" Inside except block ")
        return x
    else:
        print(f" Inside else block ")
        x = 40
        return x
    finally:
        x = 30
        print(f" Inside finally block ")
        return x



print(test_func())

Output:

Inside try block
Inside finally block
30

So, why didn’t the else clause get executed here though the try block did not raise any exception. Note the return statement in the try block.The else block never got executed because the function returned even before the execution reached the else clause.

Now remove the return statement in the try block and execute the above code again.

def test_func():
    try:
        x = 10
        print(f" Inside try block ")
    except Exception as e:
        x = 20
        print(f" Inside except block ")
        return x
    else:
        print(f" Inside else block ")
        x = 40
        return x
    finally:
        x = 30
        print(f" Inside finally block ")
        return x



print(test_func())

Output:
Inside try block
Inside else block
Inside finally block
30

Summary:

  • Use extra caution when adding return in try/except/finally clauses

  • The finally clause runs whether or not the try statement produces an exception.

  • If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement

  • An else clause will get executed if the try block does not raise an exception

References:




Continue Learning