为什么breakpoint()不会在for循环中提示幂等位置?

时间:2019-12-22 04:27:08

标签: python python-3.x breakpoints pdb

我有一个非常简单的测试文件breakpoint_test.py

for i in range(3):                       #1
    breakpoint()                         #2
    print(f"first print in loop {i}")    #3
    breakpoint()                         #4
    print(f"second print in loop {i}")   #5

当我运行它并按住c继续 时,这里输出。

(py3) nakita@machine:~/tmp $ python breakpoint_test.py 
> /Users/nakita/tmp/breakpoint_test.py(3)<module>()
-> print(f"first print in loop {i}")                    <---------------looks good here!
(Pdb) c
first print in loop 0
> /Users/nakita/tmp/breakpoint_test.py(5)<module>()
-> print(f"second print in loop {i}")
(Pdb) c
second print in loop 0


> /Users/nakita/tmp/breakpoint_test.py(2)<module>()
-> breakpoint()                                         <---------------weird here!
(Pdb) c
first print in loop 1
> /Users/nakita/tmp/breakpoint_test.py(5)<module>()
-> print(f"second print in circle {i}")
(Pdb) c
second print in loop 1


> /Users/nakita/tmp/breakpoint_test.py(2)<module>()
-> breakpoint()
(Pdb) c
first print in loop 2
> /Users/nakita/tmp/breakpoint_test.py(5)<module>()
-> print(f"second print in circle {i}")
(Pdb) c
second print in loop 2

我希望breakpoint()将提示在breakpoint()之后立即执行的行。当代码首次进入for循环时,这是正确的。但是在第二个循环中迭代时,第一个breakpoint()会提示breakpoint()本身而不是-> print(f"first print in loop {i}")行。但是,循环主体中的第二个breakpoint()可以正常工作。看起来,行为是第一个breakpoint(),因为会牺牲循环体。有人知道为什么吗?

我在python 3.7.3和3.8.0上进行了测试。 我已经读过PEP 553 -- Built-in breakpoint()

1 个答案:

答案 0 :(得分:1)

Python试图在下一行的开始处开始调试,但是它的“下一行”检测有点奇怪。


默认情况下,breakpoint调用pdb.set_trace,它设置一个trace function,它将在下一个跟踪事件上执行。在这种情况下,下一个跟踪事件是'line'事件,当Python认为执行已进入新行时触发。

在循环中第一个breakpoint的第一次执行以及第二个breakpoint的所有执行中,下一个'line'事件在下一行的第一个操作码上触发。但是,在第一个breakpoint的第二次和以后的执行中,会发生一些不同的事情。

Python通过检查当前字节码指令索引是对应于行的第一条指令还是对应于最后一条执行指令之前的索引处的指令,来确定新的源代码行已开始。您可以在Python/ceval.c的{​​{3}}中看到它。

当跟踪处于活动状态时,Python仅更新instr_prev,该变量用于确定最后执行的指令。当您按下c继续执行时,跟踪功能将被停用。 (如果您使用PDB break命令设置了任何断点,它将保持活动状态,因为trace函数需要处理这些断点,但是breakpoint()调用不会通过该机制。)

在“非怪异”的断点上,Python在“一行的第一条指令”条件下触发下一行的第一个操作码上的下一行事件。

在“怪异”的断点上,instr_prev仍然具有您上次击中c时的值,因为该位置已禁用了跟踪。该值用于当前行之后的行,因此在“执行最后一条指令之前的索引处的指令”条件下,Python会在breakpoint()行的下一个操作码上触发下一个行事件。 (breakpoint()行的下一个操作码是POP_TOP以清除breakpoint的返回值。)如果Python一直在更好地跟踪上一条执行的指令,则不会触发了该事件。