【问题标题】:How does attrs fool the debugger to step into auto generated code?attrs 如何欺骗调试器进入自动生成的代码?
【发布时间】:2021-02-28 21:56:59
【问题描述】:

我正在浏览一些使用attrs 模块的代码。因此,在调试单步执行某些代码时,我最终会得到不作为实际文件存在但自动生成的源。

这个问题是关于使用什么 python 技术来实现这种行为的。用于示例的实际库或示例本身只是展示概念的具体内容。

示例

安装库

virtualenv -p python3 labgrid-venv                                                                                                                                                                      
source labgrid-venv/bin/activate                                                                                                                                                                        
                                                                                                                                                                                                                   
git clone https://github.com/labgrid-project/labgrid                                                                                                                                                               
cd labgrid                                                                                                                                                                                              
pip install -r requirements.txt                                                                                                                                                                         
pip install .                                                                                                                                                                                           

表现出行为的代码(test.py):

from labgrid import Target                                                   
from labgrid.resource import RawSerialPort                                   
 
rpi = Target("RPi")
import pdb
pdb.set_trace()
raw_serial_port = RawSerialPort(rpi, None, port="/dev/ttyUSB0", speed=115200)

执行该代码并进入RawSerialPort 会导致调试器在自动生成的源中发现自己:

(venv) project_root$ python test.py           
[0] > project_root/test.py(15)<module>()                      
-> raw_serial_port = RawSerialPort(rpi, None, port="/dev/ttyUSB0", speed=115200)                      
(Pdb++) s                                                                                             
--Call--                                                                                              
[1] > <attrs generated init labgrid.resource.serialport.RawSerialPort>(1)__init__()                   
-> def __init__(self, target, name, port=attr_dict['port'].default, speed=attr_dict['speed'].default):
(Pdb++)                                                                                               

注意def __init__的定义,实际源文件中没有这样的定义,而是attrs自动生成的。

我的问题是这里有什么 python 机制来实现这种甚至欺骗调试器的行为?

【问题讨论】:

    标签: python python-attrs


    【解决方案1】:

    PDB 使用linecache 模块来查找您正在单步执行的 Python 文件的源代码。

    所以当attrs 创建一个新方法时,它会执行以下操作:

    1. create 每种方法都有一个虚假的唯一文件名
    2. compile 生成的代码使用compile builtin 进行字节码,并告诉它代码来自那个假文件名
    3. attach 它在步骤 2 中编译的源代码到它在步骤 1 中创建的文件名 linecache

    现在,当 pdb 偶然发现它看到文件名的方法时,它会在 linecache 中查找它并具有用于单步执行的原始源代码。


    对我来说,能够逐步完成 attrs 生成的任何内容以消除魔术的概念并显示代码中发生的事情对我来说总是非常重要的。

    【讨论】:

      【解决方案2】:

      这是来自documentationRawSerialPort 的定义:

      @target_factory.reg_resource
      @attr.s(eq=False)
      class RawSerialPort(SerialPort, Resource):
          """RawSerialPort describes a serialport which is available on the local computer."""
          def __attrs_post_init__(self):
              super().__attrs_post_init__()
              if self.port is None:
                  ValueError("RawSerialPort must be configured with a port")
      

      class 语句创建的类不是最终绑定到名称RawSerialPort 的东西;这是target_factory.reg_resource 返回的任何对象。装饰器语法去糖

      class RawSerialPort(SerialPort, Resource):
          """RawSerialPort describes a serialport which is available on the local computer."""
      [docs]    def __attrs_post_init__(self):
              super().__attrs_post_init__()
              if self.port is None:
                  ValueError("RawSerialPort must be configured with a port")
      
      RawSerialPort = target_factory.reg_resource(attr.s(eq=False)(RawSerialPort))
      

      原始类首先传递给 attr.s(eq=False),它返回一个新的增强类(包含在调试器会话中引用的 __init__ 方法),并且 that 类被传递给 target_factory.reg_resource which(没有实际查找定义),返回另一个基于 attr 模块创建的类(可能是同一个类)。

      所以调试器没有被愚弄;恰恰相反,它准确地显示了正在发生的事情,这在仅查看test.py 时并不明显。

      【讨论】:

      • 让我感到困惑的是,通常调试器确实会进入装饰器,而这个示例似乎并非如此。但是您确实正确地指出它是一个类装饰器,而我只是成功地介入了函数式装饰器。让我确认一下。
      • 如果你跟踪函数的定义,它会进入装饰器。如果你正在追踪装饰的result,你会进入(比如说)装饰器returned的函数。
      • 也就是说,当你在test.py中调用pdb.set_trace()时,装饰器已经被应用了。它们在类被定义时调用,而不是在你实例化类时调用。
      • 你是对的!到目前为止,我一直假设调试器最终出现在内部函数中,因为这通常是装饰器中的第一行代码。现在我知道这不是真的
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-13
      • 1970-01-01
      • 2011-03-07
      相关资源
      最近更新 更多