python 什么时候在一条线上有多个停止点的追踪中停止在一条线上?

When does python stop on a line in tracing with more than one stopping point on a line?

考虑这两个例子:

x = 1; y = 2; z = 3

和:

for i in range(3): print(i)

在后者中,如果你在像 pdb 这样的调试器中单步执行它,你将在循环的每次迭代中停止在 print(i) 处。

但是在第一个例子中,它停止了一次。

进一步调查,反汇编多语句行,我们发现 co_lnotab 中的第一行实际上有两个条目。但是 dis.dis() 对此撒了谎。

至于 for 循环,lnotab 中只有一行,但每次交互时停止的位置(偏移量 10)位于跳跃的目标。那么即使行号没有改变,是什么触发停止?

import dis
>>> x = compile('x = 1; y = 2; z = 3', 'foo', 'exec')
>>> x.co_lnotab
b'\x04\x00\x04\x00'
>>> dis.dis(x)
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (x)
              4 LOAD_CONST               1 (2)
              6 STORE_NAME               1 (y)
              8 LOAD_CONST               2 (3)
             10 STORE_NAME               2 (z)
             12 LOAD_CONST               3 (4)
             14 STORE_NAME               3 (a)
             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE
>>> y = compile('for i in range(3): print(i)', 'foo', 'exec')
>>> y.co_lnotab
b'\x0e\x00'
>>> dis.dis(y)
  1           0 SETUP_LOOP              24 (to 26)
              2 LOAD_NAME                0 (range)
              4 LOAD_CONST               0 (3)
              6 CALL_FUNCTION            1
              8 GET_ITER
        >>   10 FOR_ITER                12 (to 24)
             12 STORE_NAME               1 (i)
             14 LOAD_NAME                2 (print)
             16 LOAD_NAME                1 (i)
             18 CALL_FUNCTION            1
             20 POP_TOP
             22 JUMP_ABSOLUTE           10
        >>   24 POP_BLOCK
        >>   26 LOAD_CONST               1 (None)
             28 RETURN_VALUE
>>>

这个逻辑的源代码在哪里?我查看了 Python C 代码,但我找不到它,比如在 ceval.c 中寻找 PyTrace_LINE.

编辑:

根据 user2357112 的回答并阅读那里建议的代码,我能够验证代码的每个语句中有一个 can stop/trace。 我使用新的 Python 汇编程序 pyc-xasm 将字节码修改为:

  2:
            LOAD_CONST           (1)
            STORE_NAME           (x)
            JUMP_FORWARD         L2B

L2A:
  2:
            LOAD_CONST           (2)
            STORE_NAME           (y)
            JUMP_FORWARD         L2D
L2B:
            JUMP_ABSOLUTE        L2A

L2C:
  2:
            LOAD_CONST           (3)
            STORE_NAME           (z)
            JUMP_FORWARD         L3
L2D:
            JUMP_ABSOLUTE        L2C

L3:
  3:
            LOAD_NAME            (x)
            LOAD_NAME            (y)
            BINARY_ADD
            LOAD_NAME            (z)
            BINARY_ADD
            PRINT_ITEM
            PRINT_NEWLINE
            LOAD_CONST           (None)
            RETURN_VALUE

和运行这将导致Python在每一行之前停止。

PDB 跟踪使用通过 sys.settrace 设置的跟踪功能。有很多事件会触发trace函数,但是你看的都是line事件:

'line'
The interpreter is about to execute a new line of code or re-execute the condition of a loop. The local trace function is called; arg is None; the return value specifies the new local trace function. See Objects/lnotab_notes.txt for a detailed explanation of how this works.

如文档所述,您可以在 Objects/lnotab_notes.txt 中查看行事件触发器的更详细说明。最相关的部分是

We fix this by only calling the line trace function for a forward jump if the co_lnotab indicates we have jumped to the start of a line, i.e. if the current instruction offset matches the offset given for the start of a line by the co_lnotab. For backward jumps, however, we always call the line trace function, which lets a debugger stop on every evaluation of a loop guard (which usually won't be the first opcode in a line).

因此 PDB 将在一行的开头暂停,或者如果执行在代码中向后跳转。


如果想看触发行事件的源码,在maybe_call_line_trace. PDB's source code is, predictably, under Lib/pdb.pyPython/ceval.c