Try-except-finally paradox

J

Jessica Ross

I found something like this in a StackOverflow discussion..... try:
.... raise Exception("Exception raised during try")
.... except:
.... print "Except after try"
.... return True
.... finally:
.... print "Finally"
.... return False
.... return None
....Except after try
FinallyFalse

I understand most of this.
What I don't understand is why this returns False rather than True. Does the finally short-circuit the return in the except block?
 
I

Ian Kelly

I found something like this in a StackOverflow discussion.
... try:
... raise Exception("Exception raised during try")
... except:
... print "Except after try"
... return True
... finally:
... print "Finally"
... return False
... return None
...
Except after try
Finally
False

I understand most of this.
What I don't understand is why this returns False rather than True. Does
the finally short-circuit the return in the except block?

The docs don't seem to specify what happens in this case, but this behavior
is intuitive to me. If the except suite had raised an exception instead of
returning, the return in the finally would suppress that. The generalized
rule appears to be that the control flow specification executed later
overrides the earlier.
 
A

Andrew Berg

I found something like this in a StackOverflow discussion.
... try:
... raise Exception("Exception raised during try")
... except:
... print "Except after try"
... return True
... finally:
... print "Finally"
... return False
... return None
...
Except after try
Finally
False

I understand most of this.
What I don't understand is why this returns False rather than True. Does the finally short-circuit the return in the except block?
My guess would be that the interpreter doesn't let the finally block get skipped under any circumstances, so the return value gets set to
True, but then it forces the finally block to be run before returning, which changes the return value to False.
 
W

wxjmfauth

Le jeudi 30 janvier 2014 06:56:16 UTC+1, Jessica Ross a écrit :
I found something like this in a StackOverflow discussion.


... try:

... raise Exception("Exception raised during try")

... except:

... print "Except after try"

... return True

... finally:

... print "Finally"

... return False

... return None

...


Except after try

Finally


False



I understand most of this.

What I don't understand is why this returns False rather than True. Does the finally short-circuit the return in the except block?

========

The paradox is, in my mind, that the fct paradox() is
programmed to be paradoxal.

Compare with:
.... try:
.... a = 1 / i
.... print('Process')
.... except ZeroDivisionError:
.... print("ZeroDivisionError")
.... a = '?'
.... except Exception:
.... print("Exception")
.... a = '?'
.... finally:
.... print("Finally")
.... return a
....
Process
Finally
0.5
ZeroDivisionError
Finally
'?'
Exception
Finally
'?'

jmf
 
D

Dave Angel

Jessica Ross said:
I found something like this in a StackOverflow discussion.
... try:
... raise Exception("Exception raised during try")
... except:
... print "Except after try"
... return True
... finally:
... print "Finally"
... return False
... return None
...
Except after try
Finally
False

I understand most of this.
What I don't understand is why this returns False rather than True. Does the finally short-circuit the return in the except block?

The finally has to happen before any return inside the try or the
except. And once you're in the finally clause you'll finish it
before resuming the except clause. Since it has a return, that
will happen before the other returns. The one in the except block
will never get reached.

It's the only reasonable behavior., to my mind.
 
C

Chris Angelico

The finally has to happen before any return inside the try or the
except. And once you're in the finally clause you'll finish it
before resuming the except clause. Since it has a return, that
will happen before the other returns. The one in the except block
will never get reached.

It's the only reasonable behavior., to my mind.

It's arguable that putting a return inside a finally is unreasonable
behaviour, but that's up to the programmer. A finally clause can be
used to do what might be done in C++ with a destructor: "no matter how
this function/block exits, do this as you unwind the stack". In C++, I
might open a file like this:

void func()
{
ofstream output("output.txt");
// do a whole lot of stuff ...
// at the close brace, output.~output() will be called, which will
close the file
}

In Python, the equivalent would be:

def func():
try:
output = open("output.txt", "w")
# do a whole lot of stuff ...
finally:
output.close()

(Actually, the Python equivalent would be to use a 'with' clause for
brevity, but 'with' uses try/finally under the covers, so it comes to
the same thing.) The concept of the finally clause is: "Whether
execution runs off the end, hits a return statement, or throws an
exception, I need you do this before anything else happens". Having a
return statement inside 'finally' as well as in 'try' is a bit of a
corner case, since you're now saying "Before you finish this function
and return something, I need you to return something else", which
doesn't usually make sense. If you think Python's behaviour is
confusing, first figure out what you would expect to happen in this
situation :)

ChrisA
 
M

MRAB

It's arguable that putting a return inside a finally is unreasonable
behaviour, but that's up to the programmer. A finally clause can be
used to do what might be done in C++ with a destructor: "no matter how
this function/block exits, do this as you unwind the stack". In C++, I
might open a file like this:

void func()
{
ofstream output("output.txt");
// do a whole lot of stuff ...
// at the close brace, output.~output() will be called, which will
close the file
}

In Python, the equivalent would be:

def func():
try:
output = open("output.txt", "w")
# do a whole lot of stuff ...
finally:
output.close()

(Actually, the Python equivalent would be to use a 'with' clause for
brevity, but 'with' uses try/finally under the covers, so it comes to
the same thing.) The concept of the finally clause is: "Whether
execution runs off the end, hits a return statement, or throws an
exception, I need you do this before anything else happens". Having a
return statement inside 'finally' as well as in 'try' is a bit of a
corner case, since you're now saying "Before you finish this function
and return something, I need you to return something else", which
doesn't usually make sense. If you think Python's behaviour is
confusing, first figure out what you would expect to happen in this
situation :)
One of the reasons that the 'with' statement was added was to prevent
the mistake that you've just done. ;-)

What if the file can't be opened?
 
C

Chris Angelico

One of the reasons that the 'with' statement was added was to prevent
the mistake that you've just done. ;-)

What if the file can't be opened?

Yeah, whoops. The open shouldn't be inside try/finally.

def func():
output = open("output.txt", "w")
try:
# do a whole lot of stuff ...
finally:
output.close()

But my point still stands, I believe :)

ChrisA
 
R

Rotwang

My guess would be that the interpreter doesn't let the finally block
get skipped under any circumstances, so the return value gets set to
True, but then it forces the finally block to be run before returning,
which changes the return value to False.

Mine too. We can check that the interpreter gets as far as evaluating
the return value in the except block:
try:
raise Exception("Raise")
except:
print("Except")
return [print("Return"), True][1]
finally:
print("Finally")
return False
return None
Except
Return
Finally
False
 
E

Ethan Furman

My guess would be that the interpreter doesn't let the finally block
get skipped under any circumstances, so the return value gets set to
True, but then it forces the finally block to be run before returning,
which changes the return value to False.

Mine too. We can check that the interpreter gets as far as evaluating the return value in the except block:

--> def paradox2():
try:
raise Exception("Raise")
except:
print("Except")
return [print("Return"), True][1]
finally:
print("Finally")
return False
return None

--> ret = paradox2()
Except
Return
Finally
--> ret
False

And just to be thorough, if the finally block doesn't have a return:

--> def paradox3():
try:
raise Exception("Raise")
except:
print("Except")
return [print("Return"), True][1]
finally:
print("Finally")
return None

--> print(paradox3())
Except
Return
Finally
True
 
T

Terry Reedy

The finally has to happen before any return inside the try or the
except. And once you're in the finally clause you'll finish it
before resuming the except clause. Since it has a return, that
will happen before the other returns. The one in the except block
will never get reached.

It's the only reasonable behavior., to my mind.

Checking with the disassembled code, it appears that the except return
happens first and is then caught and the value over-written

2 0 SETUP_FINALLY 45 (to 48)
3 SETUP_EXCEPT 16 (to 22)

3 6 LOAD_GLOBAL 0 (Exception)
9 LOAD_CONST 1 ('Exception raised during try')
12 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
15 RAISE_VARARGS 1
18 POP_BLOCK
19 JUMP_FORWARD 22 (to 44)

4 >> 22 POP_TOP
23 POP_TOP
24 POP_TOP

5 25 LOAD_GLOBAL 1 (print)
28 LOAD_CONST 2 ('Except after try')
31 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
34 POP_TOP

6 35 LOAD_CONST 3 (True)
38 RETURN_VALUE
39 POP_EXCEPT
40 JUMP_FORWARD 1 (to 44)
43 END_FINALLY45 LOAD_CONST 0 (None)

8 >> 48 LOAD_GLOBAL 1 (print)
51 LOAD_CONST 4 ('Finally')
54 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
57 POP_TOP

9 58 LOAD_CONST 5 (False)
61 RETURN_VALUE
62 END_FINALLY

10 63 LOAD_CONST 0 (None)
66 RETURN_VALUE
 
G

Göktuğ Kayaalp

I do not have any information on the topic, but I *imagine* that the
when RETURN_VALUE opcode is evaluated within the context of an except
block, it triggers a check for whether a corresponding finally block
exists and should it exist, it is triggered, much like a callback.
So, my *imaginary* algorithm works like this:

return:
if within an except block EB:
if EB has a correspoinding final block FB
run FB
else
do return
else
do return

In Jessica's example, as the finally block executes a RETURN_VALUE
opcode, and as this is *probably* a jump to the caller, the rest of the
code does not get a chance to be executed.

As I have said earlier, I by no means assert the truth of this idea I've
explained here, but it seems quite reasonable to me.

gk
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,577
Members
45,054
Latest member
LucyCarper

Latest Threads

Top