attrs 如何欺骗调试器进入自动生成的代码?

How does attrs fool the debugger to step into auto generated code?

我正在查看一些使用 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 机制来实现这种甚至欺骗调试器的行为?

这是 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语句创建的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))

原来的class先传给 attr.s(eq=False),其中 returns 一个新的、增强的 class(其中包含调试器会话中引用的 __init__ 方法),以及 that class 被传递给 target_factory.reg_resource 其中(没有实际查找定义), returns 另一个 class (可能是相同的 class)基于任何attr 模块已创建。

所以调试器没有被愚弄;恰恰相反,它 确切地 显示了正在发生的事情,仅从 test.py.

看并不明显

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

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

  1. create 每个方法的假的唯一文件名
  2. compile the generated code to bytecode using the compile builtin 并告诉它代码来自那个假文件名
  3. attach the source code it has compiled in step 2 to the filename it has created in step 1 in linecache.

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


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