• Home
  • Publications
  • Subscribe
  • Try/Finally experiments

    Can you tell what would be the output of the code just by looking at it? Which return statement will win? And why?

    def test():
    	try:
    		return 'try'
    	finally:
    		return 'finally'
    
    print(test())

    It might seem counterintuitive but the output is finally. Finnaly statement is executed every time, no matter what happened in try block.

    This lead me to do some more experiments with try/finally. First some really simple example:

    try:
    	print('try')
    finally:
    	print('finally')
    
    # output:
    # try
    # finally

    This shouldn't be surprising. First the code in try block is executed, than the finally.

    What would happen if instead of priting from try block, we return the value?

    def test():
    	try:
    		return 'try'
    	finally:
    		print('finally')
    
    print(test())
    
    # output:
    # finally
    # try

    The output is reversed! It seems the function return value is put on hold while the finally block is executed.

    It should not suprise us that all the behaviour we saw so far applies even if we introduce exceptions. But let's try anyway.

    def test():
    	try:
    		raise Exception()
    	except:
    		return 'except'
    	finally:
    		return 'finally'
    
    print(test())
    
    # output:
    # finally

    At this point we should not be surprised by this. But what would happen if we raise an exception from a finally block?

    def test():
    	try:
    		pass
    	finally:
    		raise Exception()
    
    print(test())
    
    # output:
    #Traceback (most recent call last):
    #  File "test.py", line 7, in <module>
    #	print(test())
    #  File "test6.py", line 5, in test
    #	raise Exception()
    #Exception

    An exception is simply propagated out. It that case, it should even rewrite the return value as we seen before.

    def test():
    	try:
    		return 'try'
    	finally:
    		raise Exception()
    
    print(test())
    
    # output:
    #Traceback (most recent call last):
    #  File "test7.py", line 7, in <module>
    #	print(test())
    #  File "test7.py", line 5, in test
    #	raise Exception()
    #Exception 

    And it does rewrites it and the function raises an exception instead of returning value. To test if not because exceptions take precedense over return value but because of the finally statement, we can try to switch the statements.

    def test():
    	try:
    		raise Exception()
    	finally:
    		return 'finally'
    
    print(test())
    
    # output
    # finally

    We see that finally can even overwrite exception if we return value from it. Can it also rewrite recursion exception?

    def recursion():
    	recursion()
    
    def test():
    	try:
    		recursion()
    	finally:
    		return 'finally'
    
    print(test())
    
    # output:
    # finally

    Yes, it works as expected. So what would happen if there was another recursion error in the finally block?

    def recursion():
    	recursion()
    
    def test():
    	try:
    		recursion()
    	finally:
    		return recursion()
    
    # output:
    #Traceback (most recent call last):
    #  File "test.py", line 6, in test
    #	recursion()
    #  File "test.py", line 2, in recursion
    #	recursion()
    #  File "test.py", line 2, in recursion
    #	recursion()
    #  File "test.py", line 2, in recursion
    #	recursion()
    #  [Previous line repeated 994 more times]
    #RecursionError: maximum recursion depth exceeded
    #
    #During handling of the above exception, another exception occurred:
    #
    #Traceback (most recent call last):
    #  File "test.py", line 10, in <module>
    #	print(test())
    #  File "test.py", line 8, in test
    #	return recursion()
    #  File "test.py", line 2, in recursion
    #	recursion()
    #  File "test.py", line 2, in recursion
    #	recursion()
    #  File "test.py", line 2, in recursion
    #	recursion()
    #  [Previous line repeated 994 more times]
    #RecursionError: maximum recursion depth exceeded

    Call of recursion() on line 6 raises an exception. It get's muted by the finally block which than generates exception on it's own on line 8. But we only start the recursion twice. Once on line 6 an once on line 8. What if we execute the recursions also recursively?

    def recursion():
    	recursion()
    
    def test():
    	try:
    		recursion()
    	finally:
    		test()
    
    # output:
    # Fatal Python error: Cannot recover from stack overflow.
    #
    # Current thread 0x000000011b1195c0 (most recent call first):
    #  File "test.py", line 2 in recursion
    #  File "test.py", line 2 in recursion
    #  File "test.py", line 2 in recursion
    # ...
    #  File "test.py", line 2 in recursion
    #  File "test.py", line 2 in recursion
    #  File "test.py", line 2 in recursion
    #  ...
    # Abort trap: 6

    Stack overflow. We finally broke python.