Hi Chris, On 17/11/2020 11:03 am, Chris Angelico wrote:
On Tue, Nov 17, 2020 at 9:44 PM Steven D'Aprano <steve@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-leave@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/6NKGXWLR... Code of Conduct: http://python.org/psf/codeofconduct/