4
42 -> for i in range(n):
43       foo(i)

Here I am, in the middle of a pdb session. I want to jump to the loop iteration with i = k, without evaluating foo(i) for i < k, AND then continue evaluating foo(i) for i > k. If I step forward a line to

42    for i in range(n):
43 ->    foo(i)

set i = k, and continue, then i goes back to 1 at the next iteration. Can I make it go to k + 1 instead?

Anto
  • 6,806
  • 8
  • 43
  • 65
Ian
  • 1,062
  • 1
  • 9
  • 21
  • 1
    Does the answers at http://stackoverflow.com/questions/14139817/python-in-pdb-is-it-possible-to-enable-a-breakpoint-only-after-n-hit-counts work for you? – fredtantini Sep 04 '14 at 14:41
  • @fredtantini: A conditional breakpoint at i == k will stop in the right place, but not skip evaluation of loops with i < k. – Ian Sep 04 '14 at 14:49

2 Answers2

4

You cannot 'skip forward' and back, no. You can only execute the loop as written; to get to iteration k you'll have to pass through all intermediate steps. That's because Python has no way of knowing if the state is going to alter radically between iterations, you cannot just decided to skip iterations there.

You can always execute individual expressions in the context. You can run foo(k) and observe the returned value. You can even manipulate a lot of state. You just cannot reach into the iterator object used by for to skip ahead.

If altering the code under test before running the debugger is an option, you can share the iterator with the for loop:

r = range(n)
it = iter(r)
for i in it:
    foo(i)

and now you could advance it to a later point (with next(it), for example). The problem is that you cannot rewind an iterator like that.

The next step would be to produce a custom iterator that can be rewound:

class ControllableIterator(object):
    def __init__(self, sequence):
        self.pos = 0
        self.seq = sequence
    def __iter__(self): return self
    def next(self):
        try:
            val = self.seq[self.pos]
        except IndexError:
            raise StopIteration
        self.pos += 1
        return val
    __next__ = next  # Python 3

and use that:

r = range(n)
it = ControllableIterator(r)
for i in it:
    foo(i)

Here you can set it.pos to a different value and for will happily follow along. This only works for sequences, not just any iterable.

Demo:

$ bin/python test.py 
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(19)<module>()
-> r = range(100)
(Pdb) l
 14     
 15     def foo(i):
 16         print i
 17     
 18     import pdb; pdb.set_trace()
 19  -> r = range(100)
 20     it = ControllableIterator(r)
 21     for i in it:
 22         foo(i)
 23     
[EOF]
(Pdb) n
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(20)<module>()
-> it = ControllableIterator(r)
(Pdb) 
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(21)<module>()
-> for i in it:
(Pdb) 
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(22)<module>()
-> foo(i)
(Pdb) 
0
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(21)<module>()
-> for i in it:
(Pdb) 
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(22)<module>()
-> foo(i)
(Pdb) 
1
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(21)<module>()
-> for i in it:
(Pdb) it.pos = 50
(Pdb) n
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(22)<module>()
-> foo(i)
(Pdb) 
50
> /Users/mj/Development/venvs/stackoverflow-2.7/test.py(21)<module>()
-> for i in it:
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Does that skip evaluation of foo(0), foo(1), etc? – Ian Sep 04 '14 at 14:43
  • @Ian: if you want to alter the code flow entirely, you'll have to alter the code. Debugging only lets you execute the code as written. – Martijn Pieters Sep 04 '14 at 14:44
  • Question asks about skipping the lower loops. – Ian Sep 04 '14 at 14:44
  • No way to change some attribute of the iterable created by `range(n)` in the `for` line? – Ian Sep 04 '14 at 14:45
  • @Ian: Not the standard iterable, no. `for` doesn't share that iterable if it creates one. You can create your own, one that breaks the normal rules, and pass that to the `for` loop. – Martijn Pieters Sep 04 '14 at 14:53
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/60632/discussion-between-ian-and-martijn-pieters). – Ian Sep 04 '14 at 15:19
1

I can't test this right now, but I believe that you can use the condition command:

condition bpnumber [condition]
Condition is an expression which must evaluate to true before the breakpoint is honored. If condition is absent, any existing condition is removed; i.e., the breakpoint is made unconditional.

condition <insert line num> i == k

James Mertz
  • 8,459
  • 11
  • 60
  • 87
  • 1
    The OP wants to skip the intermediary iterations altogether. – Martijn Pieters Sep 04 '14 at 14:53
  • 1
    @MartijnPieters Ah I see. I'll leave this here in case this is useful to someone else that is looking to do what I've described. – James Mertz Sep 04 '14 at 17:19
  • Thanks - this saved me a good deal of time. FYI it's `condition i == k`, not line number, so the breakpoint should already exist. It's also possible to set the condition at time of creation: https://docs.python.org/3/library/pdb.html – berkelem Mar 18 '20 at 15:43