【问题标题】:exec code in original local scope of frame在框架的原始本地范围内执行代码
【发布时间】:2010-08-04 16:43:47
【问题描述】:

我编写了一个远程 Python 调试器,我需要的功能之一是在断点处停止时执行任意代码。我的调试器使用以下代码来执行从远程调试器接收到的代码:

exec (compile(code, '<string>', 'single') , frame.f_globals, frame.f_locals)

这在大多数情况下都可以正常工作,但我注意到了几个问题。

  1. 赋值语句实际上并未应用于原始的本地字典。这可能是因为 f_locals 应该是只读的。

  2. 如果在类方法中停止,访问受保护的属性(以双下划线开头的名称)将不起作用。我假设这是由于 Python 对受保护属性执行的名称修改。

所以我的问题是,有没有办法绕过这些限制?我可以欺骗 Python 使其认为代码是在该帧的实际本地范围内执行的吗?

我正在使用 CPython 2.7,我愿意接受特定于该版本的解决方案/hack。

【问题讨论】:

    标签: python debugging exec frame


    【解决方案1】:

    赋值语句实际上并不是 适用于原来的当地人 字典。这可能是由于 f_locals 应该是的事实 是只读的。

    不完全是,但函数的字节码不会查看locals,使用相当简单但至关重要的优化,即局部变量位于简单数组中,避免运行时查找。避免这种情况的唯一方法(并使函数 muchmuch 变慢)是编译不同的代码,例如以exec '' 开头的代码强制编译器避免优化(在 Python 2 中;在 Python 3 中不行)。如果您需要使用现有的字节码,那么您就不走运了:没有没有方法可以实现您想要的。

    如果在类方法中停止, 访问受保护的属性(名称 以双下划线开头) 不行。我假设这是由于 Python 执行的名称修改 关于受保护的属性。

    是的,所以这个问题确实允许解决方法:在名称前加上_Classname 以模仿编译器的功能。请注意,双下划线前缀意味着 privateprotected 将是一个 single 下划线(不会给您带来麻烦)。私有名称专门用于避免在子类中绑定名称的意外类(并为此目的而体面地工作,尽管不是完美的,也不是为了其他任何目的;-)。

    【讨论】:

    • 不是我希望的答案,但感谢 Alex 的澄清。
    【解决方案2】:

    我不确定我是否理解正确,但 exec 确实在代码中使用赋值填充了 locals 参数:

    >>> loc = {}
    >>> exec(compile('a=3', '<string>', 'single'), {}, loc)
    >>> loc
    {'a': 3}
    

    也许f_locals 不允许写入。

    【讨论】:

    • 根据我的经验, f_locals 是可写的,但它似乎是原始 locals() 字典的副本。因此对其进行的任何更改都不会影响原始范围。
    【解决方案3】:

    在断点处停止时执行任意代码...我可以欺骗 Python 使其认为代码是在该帧的实际本地范围内执行的吗?

    Python 调试器 pdb 允许这样做。例如,假设您正在调试文件tests/scopeTest.py,并且您的程序中有以下行,该变量尚未在程序本身中声明:

    print (NOT_DEFINED_IN_PROGRAM)
    

    这样运行代码python tests/scopeTest.py 会导致:

    NameError: name 'NOT_DEFINED_IN_PROGRAM' is not defined
    

    现在您想在调试器中停止在该行时定义该变量,并让程序继续执行,使用该变量,就像它一直在程序中定义一样。换句话说,您希望在该范围内实现更改,以便您可以继续执行该更改永久。其实是可以的:

    $ python -m pdb tests/scopeTest.py
    > /home/user/tests/scopeTest.py(1)<module>()
    -> print (NOT_DEFINED_IN_PROGRAM)
    (Pdb) 'NOT_DEFINED_IN_PROGRAM' in locals()
    False
    (Pdb) NOT_DEFINED_IN_PROGRAM = 5
    (Pdb) 'NOT_DEFINED_IN_PROGRAM' in locals()
    True
    (Pdb) step
    5
    

    Pdb 通过其default 函数中的compileexec 执行此操作,相当于:

    code = compile(line + '\n', <stdin>, 'single')
    exec(code, self.curframe.f_globals, self.curframe_locals)
    

    其中self.curframe 是一个特定的框架。现在,self.curframe_locals 不是self.curframe.f_locals,因为正如setup 函数所说:

    # The f_locals dictionary is updated from the actual frame
    # locals whenever the .f_locals accessor is called, so we
    # cache it here to ensure that modifications are not overwritten.
    self.curframe_locals = self.curframe.f_locals
    

    希望对您有所帮助,这就是您的意思!

    请注意,即便如此,您是否希望,例如,将正在调试的程序上下文中的函数替换为猴子补丁版本,例如:

    newGlobals['abs'] = myCustomAbsFunction
    exec(code, newGlobals, locals)
    

    myCustomAbsFunction 的范围不是用户程序,而是定义该函数的上下文,即调试器!也有一种方法可以解决这个问题,但由于没有特别要求,所以暂时将其作为练习留给读者。 ^__^

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多