-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
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
Wrong trace with multiple decorators (linenumber wrong in frame) #82152
Comments
When applying multiple decorators to a function, a traceback from the top generator shows the bottom generator instead. ---------------- def printingdec(f):
raise Exception()
return f
def dummydec(f):
return f
@printingdec
@dummydec
def foo():
pass gives
Traceback (most recent call last):
File "bug.py", line 9, in <module>
@dummydec
File "bug.py", line 2, in printingdec
raise Exception()
Exception
instead of
Traceback (most recent call last):
File "bug.py", line 8, in <module>
@printingdec
File "bug.py", line 2, in printingdec
raise Exception()
Exception Digging around with sys._getframe() it seems that the frame's linenumber is set wrong internally, leading to the wrong line being displayed. |
In Python 3.8+ I got: Traceback (most recent call last):
File "/home/serhiy/py/cpython/issue37971.py", line 10, in <module>
def foo():
File "/home/serhiy/py/cpython/issue37971.py", line 2, in printingdec
raise Exception()
Exception The traceback refers to the line where the function is defined. |
Digging around with the disassembler shows that this originates in the bytecode. import dis
src = """
def printingdec(f):
raise Exception()
return f
def dummydec(f):
return f
@printingdec
@dummydec
def foo():
pass
"""
code = compile(src,filename="bug.py",mode='exec')
print(dis.dis(code))
--------- gives on 3.6: 6 8 LOAD_CONST 2 (<code object dummydec at 0x7f86c84b8660, file "bug.py", line 6>) 9 16 LOAD_NAME 0 (printingdec) 10 18 LOAD_NAME 1 (dummydec) and on 3.9: 6 8 LOAD_CONST 2 (<code object dummydec at 0x7f5e13aacb30, file "bug.py", line 6>) 9 16 LOAD_NAME 0 (printingdec) 10 18 LOAD_NAME 1 (dummydec) 11 20 LOAD_CONST 4 (<code object foo at 0x7f5e13aacbe0, file "bug.py", line 9>) Disassembly of <code object printingdec at 0x7f5e13aaca80, file "bug.py", line 2>: 4 6 LOAD_FAST 0 (f) Disassembly of <code object dummydec at 0x7f5e13aacb30, file "bug.py", line 6>: Disassembly of <code object foo at 0x7f5e13aacbe0, file "bug.py", line 9>: The change from 3.6 seems to be that a new line number is introduced for instruction 20, loading the function code, which seems reasonable. |
It makes sense. If write decorators as explicit function calls: decorated = (
deco1(
deco2(
original
)
)
) The line number of decorator itself will be used for CALL_FUNCTION: 2 0 LOAD_NAME 0 (deco1) 3 2 LOAD_NAME 1 (deco2) 4 4 LOAD_NAME 2 (original) 3 6 CALL_FUNCTION 1 2 8 CALL_FUNCTION 1 1 10 STORE_NAME 3 (decorated) But I suppose this will add more headache for coverage.py. |
After compiling 3.7 and 3.8 as well it seems that the change happened between those versions. I was a able to patch compiler.c for 3.9 to make it work (first time changing cpython internals, so no guarantees). This trips up one of the tests in test_trace however, since both the LOAD_NAME before the function def and the CALL_FUNCTION after are counted as a visit to the decorator line. However, this is also the case for your example with the decorators written out, running: def deco2(f):
return f
def go():
f = 5
f = (
deco1(
deco2(
f
)
)
)
import trace
tracer = trace.Trace(count=1,trace=0,countfuncs=0, countcallers=0)
tracer.run('go()')
for k,v in tracer.results().counts.items():
print(k,v) gives ('<string>', 1) 1 while clearly each function is only called ones. In addition, to get back to the 3.6/3.7 problem as well, on 3.6 the slight modification def deco2(f):
return f
f = 5
f = (
deco1(
deco2(
f
)
)
) gives
Traceback (most recent call last):
File "sixtest.py", line 12, in <module>
f
File "sixtest.py", line 2, in deco1
raise Exception()
Exception So the problem is not only with decorators, it is with function calls on multiple lines, in all versions.
|
Thank you for your patch Joran. We now use GitHub, do you mind to create a pull request? You need to use 4-spaces indentation instead of tabs. Would be nice if you have signed the PSF contributor agreement |
I ran into this problem in PyPy today, preparing a patch for CPython too (without looking at the old one). |
@ambv did you want to backport this to 3.10 or can we close it? |
Ah, I got stuck here on the conflicts between versions and discussing with Pablo it seemed they weren't trivial. Let me take one last look tomorrow, maybe I can still patch 3.10. |
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: