[Python-Dev] Re: Why does "except Ex as x" not restore the previous value of x?

2020年11月17日 03:47:34 -0800

Hi Chris,
On 17/11/2020 11:03 am, Chris Angelico wrote:
On Tue, Nov 17, 2020 at 9:44 PM Steven D'Aprano <[email protected]> wrote:
`try...except` is no different.
...
The only wrinkle in the case of `try...except` is that the error
variable is deleted, even if it wasn't actually used. If you look at the
byte-code generated, each compound try...except with an exception
variable is followed by the equivalent of:
 err = None
 del err
There really ought to be a FAQ about this, but it has something to do
with the exception object forming a long-lasting reference cycle. To
avoid that, the error variable is nuked on leaving the compound block.
That's a much bigger wrinkle than it might seem at first, though, and
I agree, this is a quite literal frequently-asked-question and should
be made clear somewhere. The except clause is special in that, if you
want the exception afterwards, you have to reassign it to another
variable; but it doesn't ACTUALLY introduce a subscope, despite kinda
looking like it does.
Interestingly, Python 3.10 has a very odd disassembly:
def f():
... try: g()
... except Exception as e:
... print(e)
...
import dis
dis.dis(f)
 2 0 SETUP_FINALLY 10 (to 12)
 2 LOAD_GLOBAL 0 (g)
 4 CALL_FUNCTION 0
 6 POP_TOP
 8 POP_BLOCK
 10 JUMP_FORWARD 44 (to 56)
 3 >> 12 DUP_TOP
 14 LOAD_GLOBAL 1 (Exception)
 16 JUMP_IF_NOT_EXC_MATCH 54
 18 POP_TOP
 20 STORE_FAST 0 (e)
 22 POP_TOP
 24 SETUP_FINALLY 20 (to 46)
 4 26 LOAD_GLOBAL 2 (print)
 28 LOAD_FAST 0 (e)
 30 CALL_FUNCTION 1
 32 POP_TOP
 34 POP_BLOCK
 36 POP_EXCEPT
 38 LOAD_CONST 0 (None)
 40 STORE_FAST 0 (e)
 42 DELETE_FAST 0 (e)
 44 JUMP_FORWARD 10 (to 56)
 >> 46 LOAD_CONST 0 (None)
 48 STORE_FAST 0 (e)
 50 DELETE_FAST 0 (e)
 52 RERAISE
 >> 54 RERAISE
 >> 56 LOAD_CONST 0 (None)
 58 RETURN_VALUE
Reconstructing approximately equivalent Python code, this would mean
it looks something like this:
def f():
 try: g()
 except Exception as e:
 try:
 print(e)
 e = None
 del e
 raise
 finally:
 e = None
 del e
 except:
 raise
 return None
The equivalent Python is closer to this:
def f():
 try:
 g()
 except Exception as e:
 try:
 print(e)
 finally:
 e = None
 del e
I don't understand why (a) the "e = None; del e" part is duplicated,
nor (b) why the RERAISE opcodes are there in two branches, but I guess
it works out best to be explicit in there?
The reason for the seeming verbosity of the bytecode is that
try:
 body
finally:
 final
compiles to roughly:
try:
 body:
except:
 final
 raise
else:
 final
Which is why you see the duplicated sequences.
Cheers,
Mark.
Anyhow. You say that this can't come up very often because people can
go a long time without asking, but the trouble is that there are two
false interpretations that are both extremely close - either that
"except E as e:" is similar to "with E as e:", or that the except
clause creates its own scope. It's entirely possible to see supporting
evidence for your own wrong assumption and never actually know the
truth. Maybe this is going to be the next "Python has call-by-value"
vs "Python has call-by-reference" debate?
ChrisA
_______________________________________________
Python-Dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/[email protected]/message/6NKGXWLRX3SD4JQDFCOR43TAXREC33GD/
Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
Python-Dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/[email protected]/message/FNRZWHGCXOYL7QL5QUDR5NJS76RRTY3H/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to