38

I am debugging method f() that has no return in it.

class A(object):

    def __init__(self):
        self.X = []

    def f(self):            
        for i in range(10):
            self.X.append(i)

I need to see how this method modifies variable X right after it is called. To do that, I insert a return at the end of the method, and set the breakpoint there:

enter image description here

That way, as soon as the method reaches its return, I can see the value of my variable X.


This does the job, but I am pretty sure there is a better way. Editing a method or function every time I need to debug it seems silly.

Question:
Is there a different way (e.g. an option in the debugger) to set a breakpoint at the end of a method that does not have a return?

(Note that setting a breakpoint at the function call and using Step Over would not display X when mouseovering, since the function is called from a different module.)

user
  • 5,370
  • 8
  • 47
  • 75
  • 1
    This probably isn't the best answer, so I'll just post it as a comment, but there's a quick&dirty method that works in any language with monkeypatching (Python, Ruby, ObjC, Smalltalk, etc.): you can dynamically wrap `A.f` in a function that just does `return real_A_dot_f(*args, **kw)`, then put the breakpoint on that line. I've used gdb/lldb/pdb/etc. scripts to do that wrapping for me in the past. – abarnert Apr 29 '15 at 09:01
  • @abarnert I think your comment is more suited as a full answer. There might be better solutions (i don't know) but if your suggestion works, it's still a solution. – user May 01 '15 at 08:50
  • I'm not sure, because I don't think it serves the purpose of breaking while the `A.f` frame's locals are still alive to inspect... But I wrote it anyway; if it's not useful for the OP, he can always ignore it, or even downvote it. :) – abarnert May 01 '15 at 09:33

6 Answers6

6

You can add a conditional breakpoint on the last line and set the condition to be something that occurs only in the last iteration.

In this instance the condition is very easy since it's just i == 9, but it may be a lot more complex depending on your loop condition so sometimes adding a statement at the end will be the easier solution.

Conditional breakpoint in IntelliJ IDEA

That screenshot is from IntelliJ IDEA and your screenshot looks like it's from the same IDE, so just right-click the breakpoint to show the dialog and enter your condition.

If you're using some other IDE I'm sure there is capability to make a breakpoint conditional.

Update:

There is no support for breaking at the end of a method in the Python debugger, only at the start of a method:

b(reak) [[filename:]lineno | function[, condition]]

With a lineno argument, set a break there in the current file. With a function argument, set a break at the first executable statement within that function. The line number may be prefixed with a filename and a colon, to specify a breakpoint in another file (probably one that hasn't been loaded yet). The file is searched on sys.path. Note that each breakpoint is assigned a number to which all the other breakpoint commands refer.

If a second argument is present, it is an expression which must evaluate to true before the breakpoint is honored.

Without argument, list all breaks, including for each breakpoint, the number of times that breakpoint has been hit, the current ignore count, and the associated condition if any.

Community
  • 1
  • 1
Raniz
  • 10,882
  • 1
  • 32
  • 64
  • 1
    Perhaps no support in PyCharm, but you definitely **can** break at the end of a method with pdb (or ipdb). – Hugues Fontenelle May 01 '15 at 22:49
  • Then please explain how to do that, because the docs for pdb (which i cited above) doesn't tell of any way of doing that. – Raniz May 07 '15 at 00:59
  • Sure. This is in my (separate) answer. Please comment on that one if it is still unclear. – Hugues Fontenelle May 07 '15 at 07:15
  • Your break condition works in PyCharm 4 (not sure about 3). You can even set the condition to a evaluated statement, for example `i == item_list[-1]` – pferate May 08 '15 at 21:24
  • A small improvement is needed. You need to place the breakpoint in `for`, otherwise you get self.X before it gets updated with last value (unless you additionally press Step Over). – user May 27 '15 at 06:24
  • Disregard my deleted comment. This would indeed work for simple `for` loops (with the improvement i mention above). Unfortunately, the actual code can be much more complex than my example (it loops over a dict, and loop might break prematurely based on some conditions). – user May 27 '15 at 06:25
5

Your IDE is hiding what's under the hood. That is, something like

import pdb

is prepended to your script and

pdb.set_trace()

is inserted before the line onto which your placed your breakpoint. From what you say I deduce that PyCharm does not like placing breakpoints on empty lines. However pdb.set_trace() can perfectly be placed at the end of a method.

So you could insert those yourself (or write a macro) and run python -m pdb to start debugging.

(Edit) example

import pdb

class A(object):

    def __init__(self):
        self.X = []

    def f(self):
        for i in range(10):
            self.X.append(i)
        pdb.set_trace()

if __name__ == '__main__':
    a = A()
    a.f()

Debug with

$ python -m pdb test.py 
> /dev/test.py(1)<module>()
----> 1 import pdb
      2 
      3 class A(object):

ipdb> cont
--Return--
> /dev/test.py(11)f()->None
-> pdb.set_trace()
(Pdb) self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(Pdb)

ipdb can be used instead of pdb.

Hugues Fontenelle
  • 5,275
  • 2
  • 29
  • 44
  • 1
    Having some health issues, I was left with minutes before the bounty expires. Since this was the only answer saying that it can be done, i assigned the bounty here. I have not tested it yet though. If it works, i will accept this as an answer as well. – user May 26 '15 at 17:40
  • How can I make a macro that will insert `pdb.set_trace() ` at the end of a function I am testing? Would it be faster than writing `return` at the end of the function? An example would be useful for future readers. – user May 26 '15 at 17:45
  • For example, the [atom-python-debugger](https://atom.io/packages/atom-python-debugger) for Github's editor [atom](https://atom.io/) has it implemented for you by pressing – Hugues Fontenelle May 26 '15 at 21:50
  • I do not know if or how it could be done in PyCharm. – Hugues Fontenelle May 26 '15 at 21:50
  • But found this link about [PyCharm macros in the editor](https://www.jetbrains.com/pycharm/help/using-macros-in-the-editor.html) – Hugues Fontenelle May 26 '15 at 21:55
  • This is clearly more complicated than simply adding `return` statement as mentioned in the question and has no additional benefits. – Piotr Dobrogost Apr 16 '20 at 08:34
5

With pdb you can use nice combination of break function and until lineno:

Without argument, continue execution until the line with a number greater than the current one is reached.

With a line number, continue execution until a line with a number greater or equal to that is reached. In both cases, also stop when the current frame returns.

Changed in version 3.2: Allow giving an explicit line number.

You can achieve what you needed.

I modified your example a bit (so you would see that instruction gets executed although pdb reports it as "next instruction"):

01: class A(object):
02: 
03:     def __init__(self):
04:         self.X = []
05:         
06:     def f(self):         
07:         print('pre exec')
08:         for i in range(10):
09:             self.X.append(i)
10:         print('post exec')
11:             
12: a = A()
13: a.f()
14: print('Game is over')
15:

And result from running with python -m pdb test.py goes like this:

Start debugging and run it just after class declaration (so you can add named breakpoint):

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 11
> d:\tmp\stack\test.py(12)<module>()
-> a = A()

Now, break at the beginning of function:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:6

Just continue with execution until it hits breakpoint:

(Pdb) continue
> d:\tmp\stack\test.py(7)f()
-> print('pre exec')

Take advantage of "also stop when the current frame returns":

(Pdb) until 14
pre exec
post exec
--Return--

As you can see, both pre exec and post exec were printed, but when executing where you are still in f():

(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(13)<module>()
-> a.f()
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')

And all context variables are intact:

(Pdb) p self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Now with your real life example:

01: class A(object):
02:     def __init__(self):
03:         self.X = []
04:         
05:     def f(self):         
06:         for i in range(10):
07:             self.X.append(i)
08:             
09: a = A()
10: a.f()
11: print('Game is over')

Start the similar fashion as before:

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 8
> d:\tmp\stack\test.py(9)<module>()
-> a = A()
(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) cont
> d:\tmp\stack\test.py(6)f()
-> for i in range(10):

Now... Breakpoint in f.A actually means breakpoint at first statement of f.A which is unfortunately for i in... so it would break on it every time.

If you don't actually start your real code with loop, you can skip this part.

(Pdb) disable 1
Disabled breakpoint 1 at d:\tmp\stack\test.py:5

Again, use the until <end of file>:

(Pdb) until 10
--Return--
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):

And again, all frame variables are available:

(Pdb) p i
9
(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(10)<module>()
-> a.f()
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):
(Pdb)

The sad thing here is, that I wanted to try this piece of automation:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) commands 1
(com) disable 1
(com) until 11
(com) end

Which would do everything you need automatically (again, disable 1 not needed when you have at least one pre-loop statement), but according to documentation on commands:

Specifying any command resuming execution (currently continue, step, next, return, jump, quit and their abbreviations) terminates the command list (as if that command was immediately followed by end). This is because any time you resume execution (even with a simple next or step), you may encounter another breakpoint–which could have its own command list, leading to ambiguities about which list to execute.

So until just doesn't seem to work (at least for Python 3.2.5 under windows) and you have to do this by hand.

Vyktor
  • 20,559
  • 6
  • 64
  • 96
4

There is a quick&dirty solution that works on any language that supports monkeypatching (Python, Ruby, ObjC, etc.). I honestly can't remember ever needing it in Python, but I did it quite a bit in both SmallTalk and ObjC, so maybe it'll be useful for you.

Just dynamically wrap A.f in a function, like this:

real_A_f = A.f
def wrap_A_f(self, *args, **kwargs):
    result = real_A_f(self, *args, **kwargs)
    return result
A.f = wrap_A_f

In most scriptable debuggers, you should be able to write a script that does this automatically for a method by name. In pdb, which lets you execute normal Python code right in the debugger, it's especially simple.

Now you can put a breakpoint on that return result, and it's guaranteed to hit immediately after the real A.f returns (even if it returns in the middle or falls off the end without a return statement).

A few things you may want to add:

  • If you also want to catch A.f raising, put a try: and except: raise around the code, and add a breakpoint on the raise.
  • For Python 2.x, you may want to wrap that up with types.MethodType to make a real unbound method.
  • If you only want a breakpoint on a specific A instance, you can either use a conditional breakpoint that checks self is a, or use types.MethodType to create a bound instance and store that as a.f.
  • You may want to use functools.wraps if you want to hide the wrapper from the rest of the code (and from your debugging, except in the cases where you really want to see it).
  • Since pdb lets you execute dynamic code right in the live namespace, you can put a wrap_method function somewhere in your project that does this, and then, at the prompt, write p utils.wrap_method(A, 'f'). But if you wrap multiple methods this way, they're going to share the same breakpoints (inside the wrapper function defined inside wrap_method). Here I think a conditional breakpoint is the only reasonable option.
  • If you want access to the real A.f's locals from the wrapper's breakpoint, that's a lot harder. I can think of some very hacky options (e.g., exec(real_A_f.__code__, real_A_f.globals()), but nothing I'd be happy with.
abarnert
  • 354,177
  • 51
  • 601
  • 671
1

You have a few options here.

  1. Add a break point to the last line in the function.

In this case, the last line is within a loop, so you would have to iterate over each item in the loop.

  1. Add a break point where the function is being called.

This will stop the debugger prior to the function being called, but you can "Step Over" the function to see the value of A.x after A.f() is called.

  1. Add a temporary statement the end of the function to break at

This trick would work if your function ends in a loop and there are multiple places the function is called or you don't want to track down the function call.

You can add a simple statement to the end of the function for debugging purposes and add a break point there.

def f(self):            
    for i in range(10):
        self.X.append(i)
    debug_break = 1
pferate
  • 2,067
  • 1
  • 16
  • 18
  • 1. doesn't work since the function has a `for` loop. 2. Doesn't work because the function is called from a different module, so when i mouseover `X` after i "step over", the value of `X` can't be displayed. 3. is what i am currently doing. – user May 01 '15 at 06:50
1

Why not just leave the return in there? Or a return None. It's implicit anyway, the interpreter/compiler will do the same thing regardless:

In fact, even functions without a return statement do return a value, albeit a rather boring one. This value is called None (it’s a built-in name).

[source: Python Tutorial 4.6].

skolsuper
  • 629
  • 1
  • 6
  • 21
  • 2
    I have many functions like those. As mentioned in my question, editing them with `return` whenever I debug them is something i want to avoid, if there is a better (faster) way to achieve my goal. – user May 26 '15 at 17:30
  • I'm saying leave the return statements there in production, they don't cost anything. – skolsuper May 27 '15 at 01:34