我正在调试一个Python脚本,想要监视一个变量是否发生变化(类似于在gdb中观察内存地址)。有没有方法可以实现这个目的?
... 类似于在 GDB 中观察内存地址 ...
要在命中 断点 时查看变量,可以使用 commands
命令。例如,在命中第一个断点时打印 some_variable
(可以参考 PDB 文档的典型示例)。
(Pdb) commands 1
(com) print(some_variable)
(com) end
(Pdb)
此外,您可以使用condition
命令来确保断点仅在变量取特定值时被触发。
例如:
(Pdb) condition 1 some_variable==some_value
您可以使用追踪/分析功能,通过使用sys.settrace
查看正在执行的操作码,逐步检查事物。
以下是一些代码,可供您开始使用:
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()
你可能需要监视所有的STORE_*
操作码。https://docs.python.org/3/library/dis.html
j
似乎被用作 jump
的缩写。我想打印出 j
,我可以这样做吗或者需要改变变量名? - Ego ren对于Python 3:
您可以使用pdb的显示功能。
一旦触发断点,只需键入
ipdb> display expression
示例:
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>
正如您所看到的,每次键入"display",它都会打印出所有手表(表达式)。 您可以使用内置函数undisplay
删除某些观察记录。
您还可以使用pp expression来美化表达式(非常有用)
break <行号>
,每次 c
ontinue 时都会显示你的显示。显示非常方便。 - rafaelpivato这里有一个非常hacky的方法使用 pdb
进行调试。这些命令可以放在你的 ~/.pdbrc
文件中,每次使用 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"])
这将添加两个命令,nextwatch
和stepwatch
,分别以变量名varname作为参数。如果可能的话,它们将为当前帧的本地变量varname创建一个浅复制,并继续执行next
或step
,直到该名称所指向的内容发生更改。
这在CPython 2.7.2中可以工作,但依赖于一些pdb
内部实现,因此在其他地方可能会出现问题。
.pdbrc
无法正常工作。即使我将上面的代码粘贴到(pdb):
环境中,当我运行stepwatch
或nextwatch
时,它会显示__dict
和其他内容未定义。你能否更新你的代码以适用于Python 3.5?谢谢。 - Daniel一个可能的解决方案是使用pdb++:
pip install pdbpp
然后,使用修饰符@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
在任何Foo对象的属性bar发生更改时,pdb
都会中断。
注意事项
只有对基础__setattr__
方法的调用才会触发断点。这意味着f.bar = 'XYZ'
和setattr(f, 'XYZ')
将起作用,但操纵bar
对象不会触发断点:
f.bar = []
f.bar.append(7) # will NOT trigger breakpoint
f.bar = 2
f.bar += 5 # will trigger breakpoint
注意:@break_on_setattr
不是标准的pdb
模块的一部分。 pdb
被pdbpp
包覆盖/猴子补丁。
在pdb.set_trace()
之后,您还可以通过其类来包装现有对象:
(Pdb++) import pdb
(Pdb++) pdb.break_on_setattr('tree_id')(self.__class__)
(Pdb++) continue
这是我对 Michael Hoffman 的解决方案的版本,适用于ipdb和Python 3.7(更改涉及检查类类型和字典的has_key):
!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"])
如果您要在Python 3.7和pdb中使用它,请按照以下方式更改__Pdb的定义:
!global __Pdb; from pdb import Pdb as __Pdb
两个现有的答案都存在一些基本问题。
直接在pdb中执行,或者使用一些技巧来实现观察点,在你处于相同作用域时才能正常工作。当你跳出该帧时,它将不起作用。
def change(var):
var.append(1)
a = []
change(a)
它不能捕获函数内发生的事情,只有在change(a)
之后才知道变量a已经被更改。这在函数很简单的情况下有效,但实际上,函数可能会非常深入,有价值的东西就在里面。或者更糟糕的是,a
可能被返回并传递,但在当前范围内永远不会改变。那么你就永远无法捕获它。
__setattr__
方法仅适用于您可以创建自己的类的情况。它无法处理我上面提到的简单情况。对于许多情况而言,拥有一个新类型的对象是不可接受的。例如,如果您正在使用内置的dict
或list
。
我建议使用一个叫做watchpoints的库。
它比pdb(如果您还不熟悉它)更容易使用,并且覆盖的情况比本文中任何一种解决方案都要多得多。
您唯一需要做的就是watch
您想要监视的变量。
from watchpoints import watch
a = []
watch(a)
a.append(1) # will print the changes
> <module> (my_script.py:5):
> a = 1
a:
0
->
1
它可以与内置类型(包括不可变的类型)、自定义类以及属性和索引一起使用。例如,您可以执行watch(my_obj.my_attr)
,它会正常工作。
您提到了pdb
,因此可能有一些额外的东西您想要实现,而不仅仅是知道变量已更改。 watchpoints
支持 pdb
拉起,就像 breakpoints()
一样工作。
您需要通过以下方式配置 watchpoints
watch.config(pdb=True)
然后像上面那样 watch
变量。当该变量发生改变时,将弹出 pdb
。你可以使用 q(uit)
命令退出 pdb
并在下一次变量改变时再次弹出。
您可以参考github页面了解更多有关该库的用法。