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

Viewed 226

Consider these two examples:

x = 1; y = 2; z = 3

and:

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

In the latter, if you step through this in a debugger like pdb, you'll it will stop at the print(i) at each iteration of a loop.

However in the first example, it stops once.

Investigating further, disassembling the multi-statement line we see that there are in fact two entries for the first line in co_lnotab. But dis.dis() lies about this.

As for the for loop there is just the one line in the lnotab but the place that you stop at on each interation, offset 10, is at the target of a jump. So is that what triggers stopping even though the line number hasn't changed?

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
>>>

Where is the source code for this logic? I've looked in the Python C code, but I can't find it, say in ceval.c looking for PyTrace_LINE.

Edit:

Based on user2357112's answer and reading the code suggested there, I was able to verify that one can stop/trace on each statement of the code. I used my new Python assembler, pyc-xasm, to modify the bytecode to this:

  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

And running this will cause Python to stop before each line.

1 Answers
Related