【发布时间】:2011-10-05 23:36:28
【问题描述】:
我正在调试一个 python 脚本,我想观察一个变量的变化(就像你可以在 gdb 中观察一个内存地址一样)。有没有办法做到这一点?
【问题讨论】:
-
@WayneWerner: stackoverflow.com/questions/46822154/…
我正在调试一个 python 脚本,我想观察一个变量的变化(就像你可以在 gdb 中观察一个内存地址一样)。有没有办法做到这一点?
【问题讨论】:
...就像您可以在 gdb 中查看内存地址...
要在遇到断点时查看变量,可以使用commands 命令。例如。在遇到断点 #1 (canonical example from pdb doc) 时打印 some_variable。
(Pdb) commands 1
(com) print(some_variable)
(com) end
(Pdb)
此外,您可以使用condition 命令确保仅在变量取特定值时才触发断点。
例如:
(Pdb) condition 1 some_variable==some_value
您可以使用跟踪/分析功能使用sys.settrace 逐步检查事物并检查正在执行的操作码。
https://docs.python.org/3/library/sys.html#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,我可以这样做或更改变量名吗?
对于 Python 3:
你可以使用pdb的display功能
一旦你到达断点,只需键入
ipdb> 显示 表达式
示例:
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表达式来美化表达式(非常有用)
【讨论】:
break <lineno>,每次continue 都会显示您的显示。显示非常方便。
这是使用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代码吗?谢谢
一个可能的解决方案是使用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
此处pdb 将在任何 Foo 对象上的属性 bar 发生任何更改时中断。
注意事项
只有底层__setattr__-方法的调用才会触发断点。这意味着f.bar = 'XYZ' 和setattr(f, 'XYZ') 可以工作,但操作bar-object 不会触发断点:
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
【讨论】:
这是我为 ipdb 和 python 3.7 采用的Michael Hoffman's 解决方案版本(与检查类类型和字典的 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 中执行,或者使用一些 hack 来实现观察点,只有在相同范围内才能工作。出框就不行了。
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 页面了解更多关于该库的使用情况。
【讨论】: