开发者

How do you watch a variable in pdb

I'm debugging a python 开发者_JAVA技巧script, and I want to watch a variable for a change (much like you can watch a memory adress in gdb). Is there a way to do this?


data breakpoints with pdb

...much like you can watch a memory address in gdb...

  • GDB uses data breakpoints, this is made easy with hardware support (hardware watchpoints), this typically involves marking the memory pages read-only which then trips an exception handler on memory access. When hardware watchpoints are not available it uses software watchpoints, these are only useful in single threads and are much slower.
  • PDB does not support data breakpoints, so the short answer is NO, you cannot do it with PDB out of the box.

printing variables when hitting breakpoints in pdb

For watching a variable when you are hitting a breakpoint, you can use the commands command. E.g. printing some_variable when hitting breakpoint #1 (canonical example from pdb doc).

(Pdb) commands 1
(com) print(some_variable)
(com) end
(Pdb)

Additionally, you can use the condition command to ensure the breakpoint is only hit whenever the variable takes a certain value.

eg:

(Pdb) condition 1 some_variable==some_value

other solutions

You can use tracing / profiling functions to examine things step by step using sys.settrace and checking out the opcodes being executed.

Here is some code to get you started:

import sys
import dis


def tracefn(frame, event, arg):
    if event == 'call':
        print("## CALL", frame)
        frame.f_trace_opcodes = True
    elif event == 'opcode':
        opcode = frame.f_code.co_code[frame.f_lasti]
        opname = dis.opname[opcode]
        print("## OPCODE", opname)
    return tracefn


watchme = 123

def foo():
    global watchme
    watchme = 122

sys.settrace(tracefn)

foo()

You will probably need to spy on all the STORE_* opcodes. https://docs.python.org/3/library/dis.html


For Python 3:

you can use display functionality of pdb

Once you hit the breakpoint just type

ipdb> display expression

example:

ipdb> display instance
display instance: <AppUser: dmitry4>
ipdb> display instance.id
display instance.id: 9
ipdb> display instance.university
display instance.university: <University: @domain.com>

ipdb> display

Currently displaying:
instance.university: <University: @domain.com>
instance.id: 9
instance: <AppUser: dmitry4>
ipdb> 

as you can see, each time you type display - it will print all of your watches (expressions). You can use builtin function undisplay to remove certain watch.

You can also use pp expression to prettyprint the expression (very useful)


Here is a really hacky way to do this with pdb. These commands can be put in your ~/.pdbrc for automatic loading every time you use pdb.

!global __currentframe, __stack; from inspect import currentframe as __currentframe, stack as __stack
!global __copy; from copy import copy as __copy
!global __Pdb; from pdb import Pdb as __Pdb
!global __pdb; __pdb = [__framerec[0].f_locals.get("pdb") or __framerec[0].f_locals.get("self") for __framerec in __stack() if (__framerec[0].f_locals.get("pdb") or __framerec[0].f_locals.get("self")).__class__ == __Pdb][-1]

alias _setup_watchpoint !global __key, __dict, __val; __key = '%1'; __dict = __currentframe().f_locals if __currentframe().f_locals.has_key(__key) else __currentframe().f_globals; __val = __copy(%1)

alias _nextwatch_internal next;; !if __dict[__key] == __val: __pdb.cmdqueue.append("_nextwatch_internal %1")
alias _stepwatch_internal step;; !if __dict[__key] == __val: __pdb.cmdqueue.append("_stepwatch_internal %1")

alias nextwatch __pdb.cmdqueue.extend(["_setup_watchpoint %1", "_nextwatch_internal"])
alias stepwatch __pdb.cmdqueue.extend(["_setup_watchpoint %1", "_stepwatch_internal"])

This adds two commands, nextwatch and stepwatch which each take a variable name varname as an argument. They will make a shallow copy of the current frame's local variable for varname if possible, and keep executing next or step respectively until what that name points to changes.

This works in CPython 2.7.2 but relies on some pdb internals so it will probably break elsewhere.


A possible solution is to use pdb++:

pip install pdbpp

Then "mark" the object you want to watch with the decorator @pdb.break_on_setattr:

from pdb import break_on_setattr
@break_on_setattr('bar')
class Foo(object):
    pass

f = Foo()
f.bar = 42    # the program breaks here

Here pdb will break on any change of the attribute bar on any Foo-object.

Caveats
Only invocations of the underlying __setattr__-method will trigger the breakpoint. This means that f.bar = 'XYZ' and setattr(f, 'XYZ') will work, but manipulating the bar-object will not trigger the breakpoint:

f.bar = []
f.bar.append(7) # will NOT trigger breakpoint

f.bar = 2
f.bar += 5      # will trigger breakpoint

Note: @break_on_setattr is not part of the standard pdb-module. pdb is overridden/monkey-patched by the pdbpp-package.

You can also wrap an existing object (via its class) after pdb.set_trace():

(Pdb++) import pdb
(Pdb++) pdb.break_on_setattr('tree_id')(self.__class__)
(Pdb++) continue


Here is my version of Michael Hoffman's solution adopted for ipdb and python 3.7 (changes related to checking of class type and has_key for dict):

!global __currentframe, __stack; from inspect import currentframe as __currentframe, stack as __stack
!global __copy; from copy import copy as __copy
!global __Pdb; from IPython.terminal.debugger import TerminalPdb as __Pdb
!global __pdb_list; __pdb_list = [__fr[0].f_locals.get("pdb") or __fr[0].f_locals.get("self") for __fr in __stack() if ((type(__fr[0].f_locals.get("pdb")) is __Pdb) or (type(__fr[0].f_locals.get("self")) is __Pdb))]
!global __pdb; __pdb = __pdb_list[0]
alias _setup_watchpoint !global __key, __dict, __val; __key = '%1'; __dict = __currentframe().f_locals if (__key in __currentframe().f_locals) else __currentframe().f_globals; __val = __copy(%1)
alias _nextwatch_internal next;; !if __dict[__key] == __val: __pdb.cmdqueue.append("_nextwatch_internal %1")
alias _stepwatch_internal step;; !if __dict[__key] == __val: __pdb.cmdqueue.append("_stepwatch_internal %1")
alias nextwatch __pdb.cmdqueue.extend(["_setup_watchpoint %1", "_nextwatch_internal"])
alias stepwatch __pdb.cmdqueue.extend(["_setup_watchpoint %1", "_stepwatch_internal"])

To use it for python 3.7 and pdb change definition of __Pdb as given below:

!global __Pdb; from pdb import Pdb as __Pdb


Both existing answers have some essential trouble.

Directly doing it in pdb, or using some hacks to implement watchpoint, can ONLY work when you are in the same scope. It won't work when you get out of the frame.

def change(var):
    var.append(1)

a = []
change(a)

It can't catch what happens in the function, only knows after change(a), the variable a is changed. This works when the function is trivial, but in reality, the function could be so deep that what is valuable is inside. Or it could be worse, a could be returned and passed around, and never change in current scope. Then you'll never catch it.

The __setattr__ method works ONLY if you can create your own class. It can't deal with the simple situation I mentioned above. For many cases, having a new type of object is not acceptable. For example, if you are working with built-in dict or list.

I would recommend a library called watchpoints.

It is much easier to use than pdb(if you are not familiar with it already) and it covers way more situations than either of the solutions in this thread.

The only thing you need to do is to watch the variable you want to watch.

from watchpoints import watch

a = []
watch(a)
a.append(1)  # will print the changes

The output would be

> <module> (my_script.py:5):
>     a = 1
a:
0
->
1

It works with built-in types(including immutable ones), self-defined classes, and even attributes and index. For example, you can do watch(my_obj.my_attr) and it will just work.

You mentioned pdb so there might be something extra you want to achieve than simply knowing the variable is changed. watchpoints supports pdb pull up, as breakpoints() works.

You need to config watchpoints by

watch.config(pdb=True)

Then watch stuff as above. pdb will be brought up when the variable is changed. You can q(uit) the pdb and it will be brought up the next time the variable is changed.

You can refer to the github page for more usage for the library.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜