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.py
下Python/ceval.c
。
考虑这两个例子:
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 isNone
; the return value specifies the new local trace function. SeeObjects/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.py
下Python/ceval.c
。