New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python 3.8 changes how returns through finally clauses are traced #78886
Comments
When a return statement also executes a finally clause, the sequence of lines reported to the trace function is different in 3.8 than it has been before 3.8: $ cat finally_trace.py
def return_from_finally():
try:
print("returning")
return 17
finally:
print("finally") def trace(frame, event, arg):
print(frame.f_lineno, event)
return trace
import sys
sys.settrace(trace)
return_from_finally() $ python3.7 finally_trace.py
1 call
2 line
3 line
returning
4 line
6 line
finally
6 return
$ python3.8 finally_trace.py
1 call
2 line
3 line
returning
4 line
6 line
finally
4 line
4 return Is this intentional? Is it a bug? Will it change back before 3.8 is shipped? |
This affects coverage.py, as reported in this bug: nedbat/coveragepy#707 |
This is a side effect of specific optimization. If the return value is constant, it is pushed on the stack after executing the finally code (see LOAD_CONST at offset 14 below). But other opcodes at this line (POP_BLOCK and CALL_FINALLY) are executed after executing the finally code. Thus it looks like the line 4 is executed twice, but actually different opcodes marked with the same line are executed before and after executing the finally code. Disassembly of <code object return_from_finally at 0x7feff78897c0, file "<stdin>", line 1>: 3 2 LOAD_GLOBAL 0 (print) 4 10 POP_BLOCK 6 >> 18 LOAD_GLOBAL 0 (print) The benefit of this optimization is that it can make the stack smaller. This decreases the memory consumption of the Python function frame by one pointer and speeds up the Python function frame creation time (one pointer assignment less). It is tiny, but I think it is worth to keep it. I don't know what is the right solution here. |
Humm, the optimization is not related here. Even if it is not involved (replace 17 with []), the line 4 is reported twice, because RETURN_VALUE is executed after CALL_FINALLY. 4 10 BUILD_LIST 0 In 3.7 RETURN_VALUE was the opcode executed at line 4. The stack of blocks was unwinded at interpreted loop implicitly when execute RETURN_VALUE. But in 3.8 calling the finally code is explicit. |
I can't tell if you think this is something that should be fixed, or not? (Also, I'm not getting email notifications from bpo, sorry for the delay). |
I think that this can be fixed. But this is not easy. |
The new behaviour looks the more correct to me. Arguably the sequence should not include the second "4 line", but is otherwise correct. |
When I get a chance I'll see what happens with #6641 |
In master, the sequence of events is: 1 call which is the same as 3.7. When a return, break or continue statement is executed in the try suite of a try…finally statement, the finally clause is also executed ‘on the way out.’ So line 6 should be last line traced. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: