Question

When using try and except for error handling, does it matter whether you explicitly call finally or if you simply go to a new line that is dedented from the exception code? For example, is there any circumstance under which the two functions below will produce different results?

#Function 1 with finally
def final(x):
    try:
        print(x*x)

    except:
        print("Error")

    finally:
        print("End Function")


#Function 2 with new line dedent
def dedent(x):
    try:
        print(x*x)

    except:
        print("Error")

    print("End Function")

Update: Thanks for the explanation of finally running even if there is an error in the except block. One additional thing I wanted to point out is that the finally block will run even if you return an object within the except block. For example, function 3 below will print even after the return while function 4 will not.

#Function 3 with finally
def final(x):
    try:
        print(x*x)

    except:
        return 3

    finally:
        print("End Function 3")


#Function 4 with new line dedent
def dedent(x):
    try:
        print(x*x)

    except:
        return 3

    print("End Function 4")

test1 = final('test')
test2 = dedent('test')
Was it helpful?

Solution

There is the error in the except block that's been mentioned already:

def final(x):
    try:
        print(x*x)

    except:
        print("Error")
        damnit

    finally:
        print("End Function")

def dedent(x):
    try:
        print(x*x)

    except:
        print("Error")
        damnit

    print("End Function")

try:
    final("a")
except Exception as e:
    print("There was an error:", e)
#>>> Error
#>>> End Function
#>>> There was an error: global name 'damnit' is not defined

try:
    dedent("a")
except Exception as e:
    print("There was an error:", e)
#>>> Error
#>>> There was an error: global name 'damnit' is not defined

There's also return behaviour:

def final(x):
    try:
        print(x*x)

    except:
        print("Error")
        return "Return inside"

    finally:
        print("End Function")
        return "Return outside"

def dedent(x):
    try:
        print(x*x)

    except:
        print("Error")
        return "Return inside"

    print("End Function")
    return "Return outside"

try:
    final("a")
except Exception as e:
    print("There was an error:", e)
#>>> Error
#>>> End Function
#>>> 'Return outside'

try:
    dedent("a")
except Exception as e:
    print("There was an error:", e)
#>>> Error
#>>> 'Return inside'

OTHER TIPS

According to PEP 341:

try:
    block-1 ...
except Exception1:
    handler-1 ...
except Exception2:
    handler-2 ...
else:
    else-block
finally:
    final-block

The code in block-1 is executed. If the code raises an exception, the various except blocks are tested: if the exception is of class Exception1, handler-1 is executed; otherwise if it's of class Exception2, handler-2 is executed, and so forth. If no exception is raised, the else-block is executed.

No matter what happened previously, the final-block is executed once the code block is complete and any raised exceptions handled. Even if there's an error in an exception handler or the else-block and a new exception is raised, the code in the final-block is still run.

So as Martjin said, the code in finally will be executed even if the except raises another another error.

The first version will still run the finally: suite even when the except handler raises a new exception. In the second version, the final print() will not be executed in that case:

>>> def print(*args, **kw):
...     if not args or args[0] != 'End Function':
...         raise ValueError("Only print End Function!")
...     __builtins__.print(*args, **kw)
... 
>>> final(0)
End Function
Traceback (most recent call last):
  File "<stdin>", line 3, in final
  File "<stdin>", line 3, in print
ValueError: Only print End Function!

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in final
  File "<stdin>", line 3, in print
ValueError: Only print End Function!
>>> dedent(0)
Traceback (most recent call last):
  File "<stdin>", line 3, in dedent
  File "<stdin>", line 3, in print
ValueError: Only print End Function!

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in dedent
  File "<stdin>", line 3, in print
ValueError: Only print End Function!

Note that the first thing printed in final(0) is End Function; no such text is printed for dedent(0).

You never need to explicitly execute a finally: suite; it is always run, even if you were to exit the function early with return, or break out of a loop with break or use continue to skip the rest of the loop body:

>>> while True:
...     try:
...         print("In an infinite loop?")
...         break
...     finally:
...         print("Executed before the loop ends")
... 
In an infinite loop
Executed before the loop ends

Quoting from the try statement documentation:

If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, including any except and else clauses. If an exception occurs in any of the clauses and is not handled, the exception is temporarily saved. The finally clause is executed. If there is a saved exception it is re-raised at the end of the finally clause.

and

When a return, break or continue statement is executed in the try suite of a try...finally statement, the finally clause is also executed ‘on the way out.’

The difference is clear if exceptions are raised all over the place.

If only the try block raises an error then they are the same.

#Function 1 with finally
def final(x):
    try:
        print(x*x)
        raise Exception
    except:
        print("Error")    
    finally:
        print("End Function")


#Function 2 with new line dedent
def dedent(x):
    try:
        print(x*x)
        raise Exception
    except:
        print("Error")    
    print("End Function")

final(10)
dedent(10)

However. If the except block also raises an error, then the finally block is guaranteed to run.

#Function 1 with finally
def final(x):
    try:
        print(x*x)
        raise Exception
    except:
        print("Error")
        raise Exception
    finally:
        print("This will be printed")

#Function 2 with new line dedent
def dedent(x):
    try:
        print(x*x)
        raise Exception
    except:
        print("Error")
        raise Exception
    print("This will not be printed")
final(10)
dedent(10)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top