如何查看 pdb 中的变量

发布于 2024-12-08 06:49:05 字数 69 浏览 0 评论 0原文

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

I'm debugging a python script, and I want to watch a variable for a change (much like you can watch a memory adress in gdb). Is there a way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

独留℉清风醉 2024-12-15 06:49:05

使用 pdb 的数据断点

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

  • GDB 使用数据断点,通过硬件支持(硬件观察点)可以轻松实现这一点,这通常涉及将内存页标记为只读然后在内存访问时触发异常处理程序。当硬件观察点不可用时,它使用软件观察点,这些仅在单线程中有用并且速度慢得多。
  • PDB 不支持数据断点,因此简短的答案是否,您无法使用开箱即用的 PDB 来做到这一点

在 pdb 中命中断点时打印变量

要在命中断点时查看变量,可以使用 commands 命令。例如,在命中断点 #1 时打印 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

data breakpoints with pdb

...much like you can watch a memory address in gdb...

  • GDB uses data breakpoints, this is made easy with hardware support (hardware watchpoints), this typically involves marking the memory pages read-only which then trips an exception handler on memory access. When hardware watchpoints are not available it uses software watchpoints, these are only useful in single threads and are much slower.
  • PDB does not support data breakpoints, so the short answer is NO, you cannot do it with PDB out of the box.

printing variables when hitting breakpoints in pdb

For watching a variable when you are hitting a breakpoint, you can use the commands command. E.g. printing some_variable when hitting breakpoint #1 (canonical example from pdb doc).

(Pdb) commands 1
(com) print(some_variable)
(com) end
(Pdb)

Additionally, you can use the condition command to ensure the breakpoint is only hit whenever the variable takes a certain value.

eg:

(Pdb) condition 1 some_variable==some_value

other solutions

You can use tracing / profiling functions to examine things step by step using sys.settrace and checking out the opcodes being executed.

Here is some code to get you started:

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()

You will probably need to spy on all the STORE_* opcodes. https://docs.python.org/3/library/dis.html

简单爱 2024-12-15 06:49:05

对于Python 3

您可以使用显示 pdb 的功能

一旦到达断点,只需输入

ipdb>; display 表达式

示例:

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

For Python 3:

you can use display functionality of pdb

Once you hit the breakpoint just type

ipdb> display expression

example:

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> 

as you can see, each time you type display - it will print all of your watches (expressions). You can use builtin function undisplay to remove certain watch.

You can also use pp expression to prettyprint the expression (very useful)

逆光飞翔i 2024-12-15 06:49:05

这是使用 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 内部结构,因此它可能会在其他地方崩溃。

Here is a really hacky way to do this with pdb. These commands can be put in your ~/.pdbrc for automatic loading every time you use 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"])

This adds two commands, nextwatch and stepwatch which each take a variable name varname as an argument. They will make a shallow copy of the current frame's local variable for varname if possible, and keep executing next or step respectively until what that name points to changes.

This works in CPython 2.7.2 but relies on some pdb internals so it will probably break elsewhere.

-小熊_ 2024-12-15 06:49:05

一个可能的解决方案是使用 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 对象将起作用不触发断点:

f.bar = []
f.bar.append(7) # will NOT trigger breakpoint

f.bar = 2
f.bar += 5      # will trigger breakpoint

注意:@break_on_setattr 不是标准 pdb 模块的一部分。 pdbpdbpp-package 覆盖/猴子修补。

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

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

A possible solution is to use pdb++:

pip install pdbpp

Then "mark" the object you want to watch with the decorator @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

Here pdb will break on any change of the attribute bar on any Foo-object.

Caveats
Only invocations of the underlying __setattr__-method will trigger the breakpoint. This means that f.bar = 'XYZ' and setattr(f, 'XYZ') will work, but manipulating the bar-object will not trigger the breakpoint:

f.bar = []
f.bar.append(7) # will NOT trigger breakpoint

f.bar = 2
f.bar += 5      # will trigger breakpoint

Note: @break_on_setattr is not part of the standard pdb-module. pdb is overridden/monkey-patched by the pdbpp-package.

You can also wrap an existing object (via its class) after pdb.set_trace():

(Pdb++) import pdb
(Pdb++) pdb.break_on_setattr('tree_id')(self.__class__)
(Pdb++) continue
メ斷腸人バ 2024-12-15 06:49:05

这是我为 ipdb 和 python 3.7 采用的 Michael Hoffman 解决方案的版本(与检查类类型和 has_key 相关的更改) for dict):

!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

Here is my version of Michael Hoffman's solution adopted for ipdb and python 3.7 (changes related to checking of class type and has_key for dict):

!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"])

To use it for python 3.7 and pdb change definition of __Pdb as given below:

!global __Pdb; from pdb import Pdb as __Pdb
多彩岁月 2024-12-15 06:49:05

现有的两个答案都存在一些本质上的问题。

直接在 pdb 中执行此操作,或使用一些 hack 来实现观察点,仅当您处于同一范围内时才有效。当你脱离框架时,它就不起作用了。

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

a = []
change(a)

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

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

我推荐一个名为 watchpoints 的库。

它比 pdb 更容易使用(如果您还不熟悉它),并且它涵盖的情况比本线程中的任何解决方案都要多。

您唯一需要做的就是监视您想要监视的变量。

from watchpoints import watch

a = []
watch(a)
a.append(1)  # will print the changes

输出将是

> <module> (my_script.py:5):
>     a = 1
a:
0
->
1

It Works withbuilt-in types(包括不可变类型)、自定义类,甚至属性和索引。例如,您可以执行 watch(my_obj.my_attr) ,它就会正常工作。

您提到了 pdb,因此除了简单地知道变量已更改之外,您可能还想实现一些额外的目标。 watchpoints 支持 pdb 上拉,就像 breakpoints() 一样工作。

您需要通过

watch.config(pdb=True)

then watch 来配置 watchpoints ,如上所述。当变量改变时pdb将会被调出。您可以q(uit) pdb,下次变量更改时它将被调出。

您可以参考 github 页面了解该库的更多使用方法。

Both existing answers have some essential trouble.

Directly doing it in pdb, or using some hacks to implement watchpoint, can ONLY work when you are in the same scope. It won't work when you get out of the frame.

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

a = []
change(a)

It can't catch what happens in the function, only knows after change(a), the variable a is changed. This works when the function is trivial, but in reality, the function could be so deep that what is valuable is inside. Or it could be worse, a could be returned and passed around, and never change in current scope. Then you'll never catch it.

The __setattr__ method works ONLY if you can create your own class. It can't deal with the simple situation I mentioned above. For many cases, having a new type of object is not acceptable. For example, if you are working with built-in dict or list.

I would recommend a library called watchpoints.

It is much easier to use than pdb(if you are not familiar with it already) and it covers way more situations than either of the solutions in this thread.

The only thing you need to do is to watch the variable you want to watch.

from watchpoints import watch

a = []
watch(a)
a.append(1)  # will print the changes

The output would be

> <module> (my_script.py:5):
>     a = 1
a:
0
->
1

It works with built-in types(including immutable ones), self-defined classes, and even attributes and index. For example, you can do watch(my_obj.my_attr) and it will just work.

You mentioned pdb so there might be something extra you want to achieve than simply knowing the variable is changed. watchpoints supports pdb pull up, as breakpoints() works.

You need to config watchpoints by

watch.config(pdb=True)

Then watch stuff as above. pdb will be brought up when the variable is changed. You can q(uit) the pdb and it will be brought up the next time the variable is changed.

You can refer to the github page for more usage for the library.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文