我正在调试一个 python 脚本,我想观察一个变量的变化(就像你可以在 gdb 中观察一个内存地址一样)。有没有办法做到这一点?
6 回答
pdb 的数据断点
...就像您可以在 gdb 中查看内存地址一样...
- GDB 使用数据断点,这通过硬件支持(硬件观察点)变得容易,这通常涉及将内存页面标记为只读,然后在内存访问时触发异常处理程序。当硬件观察点不可用时,它使用软件观察点,这些仅在单线程中有用并且要慢得多。
- PDB 不支持数据断点,所以简短的回答是否定的,你不能用开箱即用的 PDB 来做到这一点。
在 pdb 中遇到断点时打印变量
要在遇到断点时查看变量,可以使用该commands
命令。some_variable
例如,在遇到断点 #1 时打印(来自doc的规范示例pdb
)。
(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
对于Python 3:
您可以使用pdb 的显示功能
一旦你到达断点,只需输入
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 表达式来漂亮地打印表达式(非常有用)
这是一个非常老套的方法来做到这一点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
内部结构,因此它可能会在其他地方中断。
一个可能的解决方案是使用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
将中断bar
任何 Foo 对象上的属性的任何更改。
警告
只有调用底层__setattr__
-method 才会触发断点。这意味着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
被 -package 覆盖/monkey- pdbpp
patched。
您还可以在以下之后包装现有对象(通过其类)pdb.set_trace()
:
(Pdb++) import pdb
(Pdb++) pdb.break_on_setattr('tree_id')(self.__class__)
(Pdb++) continue
这是我为 ipdb 和 python 3.7 采用的Michael Hoffman解决方案的版本(与检查类类型和字典的 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页面了解该库的更多用法。