【问题标题】:How do you watch a variable in pdb如何查看 pdb 中的变量
【发布时间】:2011-10-05 23:36:28
【问题描述】:

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

【问题讨论】:

标签: python pdb


【解决方案1】:

使用 pdb 的数据断点

...就像您可以在 gdb 中查看内存地址...

  • GDB 使用数据断点,这通过硬件 支持(硬件观察点)变得容易,这通常涉及将内存页面标记为只读,然后在内存访问时触发异常处理程序。当硬件观察点不可用时,它使用软件观察点,这些仅在单线程中有用并且速度要慢得多。
  • PDB 不支持数据断点,所以简短的回答是不,你不能用 PDB 做到这一点开箱即用。

在 pdb 中遇到断点时打印变量

要在遇到断点时查看变量,可以使用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

【讨论】:

  • 这应该是最佳答案。简单直接,教你一个pdb的简洁特性——断点命令。
  • 虽然它可能很有用,但它并不能直接回答问题,即要观察变量的变化,然后在这些变化处中断,而不仅仅是在预设断点处查看变量的状态.
  • j 这样的变量似乎被用作缩写jump。我想发送j,我可以这样做或更改变量名吗?
【解决方案2】:

对于 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 &lt;lineno&gt;,每次continue 都会显示您的显示。显示非常方便。
【解决方案3】:

这是使用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"])

这添加了两个命令,nextwatchstepwatch,每个命令都将变量名 varname 作为参数。如果可能,它们将为 varname 制作当前帧的局部变量的浅表副本,并分别继续执行 nextstep 直到该名称指向的内容发生变化。

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

【讨论】:

  • 我将宏复制到我的 ~/.pdbrc 文件中。但是 pdb 告诉我: NameError 'nextwatch' 是未定义的。我是这样摔倒的:“nextwatch'(var)
  • 我使用的是 python 3.5,mac,似乎 .pdbrc 不起作用。即使我将上面的代码粘贴到(pdb): 环境中,当我运行stepwatchnextwatch 时,它显示__dict 和其他未定义。你能更新你的python 3.5代码吗?谢谢
  • 如前所述,这可能不适用于某些其他版本的 Python。欢迎您发布 Python 3.5 版本作为新答案。
【解决方案4】:

一个可能的解决方案是使用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 模块的一部分。 pdbpdbpp 包覆盖/猴子补丁。

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

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

【讨论】:

  • 这应该是公认的答案。像魅力一样工作,超级简单。
【解决方案5】:

这是我为 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

【讨论】:

    【解决方案6】:

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

    直接在 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 页面了解更多关于该库的使用情况。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-24
      • 1970-01-01
      • 2011-10-24
      • 1970-01-01
      • 2013-08-24
      • 2021-04-27
      • 1970-01-01
      相关资源
      最近更新 更多