Hi Chris,
On 17/11/2020 11:03 am, Chris Angelico wrote:
On Tue, Nov 17, 2020 at 9:44 PM Steven D'Aprano <st...@pearwood.info> 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 -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/6NKGXWLRX3SD4JQDFCOR43TAXREC33GD/
Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at
https://mail.python.org/archives/list/python-dev@python.org/message/FNRZWHGCXOYL7QL5QUDR5NJS76RRTY3H/
Code of Conduct: http://python.org/psf/codeofconduct/