70

我正在调试一个 python 脚本,我想观察一个变量的变化(就像你可以在 gdb 中观察一个内存地址一样)。有没有办法做到这一点?

4

6 回答 6

39

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

于 2016-01-05T20:05:24.380 回答
26

对于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 表达式来漂亮地打印表达式(非常有用)

于 2016-01-09T06:19:57.870 回答
24

这是一个非常老套的方法来做到这一点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制作当前帧的局部变量的浅表副本,并继续执行nextstep分别直到该名称指向的内容发生更改。

这在 CPython 2.7.2 中有效,但依赖于一些pdb内部结构,因此它可能会在其他地方中断。

于 2011-10-06T00:14:06.470 回答
17

一个可能的解决方案是使用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不是标准模块的一部分pdbpdb被 -package 覆盖/monkey- pdbpppatched。

您还可以在以下之后包装现有对象(通过其类)pdb.set_trace()

(Pdb++) import pdb
(Pdb++) pdb.break_on_setattr('tree_id')(self.__class__)
(Pdb++) continue
于 2016-04-12T13:45:15.137 回答
3

这是我为 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
于 2020-04-01T23:31:03.463 回答
2

现有的两个答案都有一些基本问题。

直接在 pdb 中执行,或使用一些 hack 来实现观察点,只有在您处于同一范围内时才能工作。当你离开框架时它不会起作用。

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

a = []
change(a)

它无法捕捉到函数中发生的事情,只知道在change(a)变量 a 改变之后。这在函数微不足道时有效,但实际上,函数可能如此之深,以至于有价值的东西都在里面。或者它可能更糟,a可以返回和传递,并且在当前范围内永远不会改变。那你永远抓不到它。

__setattr__仅当您可以创建自己的类时,该方法才有效。它无法处理我上面提到的简单情况。在许多情况下,拥有新类型的对象是不可接受的。例如,如果您正在使用内置dictlist.

我会推荐一个名为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页面了解该库的更多用法。

于 2020-12-10T21:11:26.460 回答