在函数结束时设置调试器断点而不返回

时间:2015-04-29 08:47:27

标签: python debugging pycharm breakpoints python-3.4

我正在调试方法f(),其中没有return

class A(object):

    def __init__(self):
        self.X = []

    def f(self):            
        for i in range(10):
            self.X.append(i)

我需要看看这个方法在调用后如何修改变量X。为此,我在方法的末尾插入return,并在那里设置断点:

enter image description here

这样,只要方法到达return,我就可以看到变量X的值。

这可以胜任,但我确信有更好的方法。每次我需要调试它时编辑一个方法或函数似乎很愚蠢。

问题
是否有不同的方法(例如调试器中的选项)在没有return的方法结束时设置断点?

(请注意,在鼠标悬停时,在函数调用和使用 Step Over 时设置断点不会显示X,因为该函数是从其他模块调用的。)

5 个答案:

答案 0 :(得分:5)

您可以在最后一行添加条件断点,并将条件设置为仅在最后一次迭代中发生的条件。

在这种情况下,条件非常简单,因为它只是i == 9,但根据你的循环条件,它可能会复杂得多,所以有时在最后添加一个语句将是更容易的解决方案

Conditional breakpoint in IntelliJ IDEA

该屏幕截图来自IntelliJ IDEA,您的屏幕截图看起来就像来自同一个IDE,所以只需右键单击断点即可显示对话框并输入您的条件。

如果您正在使用其他IDE,我确信有能力使断点成为条件。

<强>更新

只有在方法开头时才支持在Python debugger中的方法结尾处中断:

  

b(reak)[[filename:] lineno |功能[,条件]]

     

使用lineno参数,在当前文件中设置一个中断。使用函数参数,在该函数中的第一个可执行语句处设置break。行号可以以文件名和冒号为前缀,以在另一个文件中指定断点(可能还有一个尚未加载的断点)。在sys.path上搜索该文件。请注意,为每个断点分配一个所有其他断点命令所引用的数字。

     

如果存在第二个参数,则它是一个表达式,在断点被接受之前必须求值为true。

     

不带参数,列出所有中断,包括每个断点,断点被击中的次数,当前忽略计数以及相关条件(如果有)。

答案 1 :(得分:3)

有一种快速而肮脏的解决方案适用于支持monkeypatching的任何语言(Python,Ruby,ObjC等)。老实说,我不记得曾经在Python中需要它,但我在SmallTalk和ObjC都做了很多,所以也许它对你有用。

只需在函数中动态包装A.f,如下所示:

real_A_f = A.f
def wrap_A_f(self, *args, **kwargs):
    result = real_A_f(self, *args, **kwargs)
    return result
A.f = wrap_A_f

在大多数可编写脚本的调试器中,您应该能够编写一个脚本,按名称自动为方法执行此操作。在pdb中,它允许您在调试器中执行正常的Python代码,它特别简单。

现在你可以在return result上设置一个断点,并确保在真正的A.f返回后立即点击(即使它在中间返回或在没有return声明。

您可能想要添加的一些内容:

  • 如果您还想抓住A.f加注,请在代码周围添加try:except: raise,并在raise上添加断点。
  • 对于Python 2.x,您可能希望用types.MethodType将其包装起来,以制作真正的未绑定方法。
  • 如果您只想在特定A实例上使用断点,则可以使用检查self is a的条件断点,或使用types.MethodType创建绑定实例并将其存储为a.f
  • 如果要隐藏代码的其余部分(以及调试,除非是您真正想要查看的情况),您可能希望使用functools.wraps
  • 由于pdb允许您在实时命名空间中执行动态代码,因此您可以在项目的某个位置放置wrap_method函数来执行此操作,然后在提示符处写入p utils.wrap_method(A, 'f')。但是如果以这种方式包装多个方法,它们将共享相同的断点(在wrap_method内定义的包装函数内)。在这里,我认为条件断点是唯一合理的选择。
  • 如果你想从包装器的断点访问真正的A.f本地人,那就更难了。我可以想到一些非常糟糕的选择(例如,exec(real_A_f.__code__, real_A_f.globals()),但我并不满意。

答案 2 :(得分:3)

您的IDE隐藏了引擎盖下的内容。 就是这样的事情

import pdb

前置于您的脚本和

pdb.set_trace()
在您放置断点的行之前插入

。 从你说的我推断PyCharm不喜欢在空行上放置断点。但是pdb.set_trace()可以完美地放在方法的末尾。

所以你可以自己插入(或写一个宏)并运行python -m pdb来开始调试。

(编辑)示例

import pdb

class A(object):

    def __init__(self):
        self.X = []

    def f(self):
        for i in range(10):
            self.X.append(i)
        pdb.set_trace()

if __name__ == '__main__':
    a = A()
    a.f()

使用

进行调试
$ python -m pdb test.py 
> /dev/test.py(1)<module>()
----> 1 import pdb
      2 
      3 class A(object):

ipdb> cont
--Return--
> /dev/test.py(11)f()->None
-> pdb.set_trace()
(Pdb) self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(Pdb)
可以使用

ipdb代替pdb

答案 3 :(得分:2)

使用pdb,您可以使用break functionuntil lineno的完美组合:

  

不带参数,继续执行直到带数字的行   达到目前的水平。

     

使用行号,继续执行,直到带有数字的行   达到或大于等于。在这两种情况下,也都停止了   当前帧返回。

     

在版本3.2中更改:允许给出明确的行号。

你可以实现你所需要的。

我稍微修改了你的例子(所以你会看到该指令被执行虽然pdb将其报告为“ next instruction ”):

01: class A(object):
02: 
03:     def __init__(self):
04:         self.X = []
05:         
06:     def f(self):         
07:         print('pre exec')
08:         for i in range(10):
09:             self.X.append(i)
10:         print('post exec')
11:             
12: a = A()
13: a.f()
14: print('Game is over')
15:

使用python -m pdb test.py运行的结果如下:

开始调试并在类声明后运行它(因此可以添加命名断点):

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 11
> d:\tmp\stack\test.py(12)<module>()
-> a = A()

现在,在函数的开头打破:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:6

继续执行直到遇到断点:

(Pdb) continue
> d:\tmp\stack\test.py(7)f()
-> print('pre exec')

利用“当当前帧返回时停止

(Pdb) until 14
pre exec
post exec
--Return--

正如您所看到的,打印了 pre exec post exec ,但在执行where时,您仍然在f():< / p>

(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(13)<module>()
-> a.f()
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')

并且所有上下文变量都是完整的:

(Pdb) p self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

现在以你的真实生活为例:

01: class A(object):
02:     def __init__(self):
03:         self.X = []
04:         
05:     def f(self):         
06:         for i in range(10):
07:             self.X.append(i)
08:             
09: a = A()
10: a.f()
11: print('Game is over')

以与以前类似的方式开始:

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 8
> d:\tmp\stack\test.py(9)<module>()
-> a = A()
(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) cont
> d:\tmp\stack\test.py(6)f()
-> for i in range(10):

现在...... f.A中的断点实际上意味着在f.A的第一个语句处断点,不幸的是for i in...因此每次都会断开它。

如果您实际上没有使用循环启动实际代码,则可以跳过此部分。

(Pdb) disable 1
Disabled breakpoint 1 at d:\tmp\stack\test.py:5

再次使用until <end of file>

(Pdb) until 10
--Return--
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):

同样,所有帧变量都可用:

(Pdb) p i
9
(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(10)<module>()
-> a.f()
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):
(Pdb)

这里令人遗憾的是,我想尝试这种自动化:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) commands 1
(com) disable 1
(com) until 11
(com) end

哪个会自动执行您需要的所有内容(当您至少有一个预循环语句时,再次,disable 1不需要),但根据commands上的文档:

  

指定任何恢复执行的命令(当前继续,步骤,下一步,返回,跳转,退出及其缩写)将终止命令列表(就好像该命令紧跟在结束之后)。这是因为每当你恢复执行时(即使使用简单的下一步或步骤),你可能会遇到另一个断点 - 它可能有自己的命令列表,导致对要执行的列表的含糊不清。

所以until似乎不起作用(至少对于Windows下的Python 3.2.5),你必须手动完成。

答案 4 :(得分:0)

为什么不把回程留在那里?或return None。无论如何,它是隐含的,无论如何,解释器/编译器都会做同样的事情:

  

事实上,即使是没有return语句的函数也会返回一个值,尽管它是一个相当无聊的值。该值称为None(它是内置名称)。

[source: Python Tutorial 4.6]