PEP 657 - 包括回溯上的细粒度错误位置

2021-05-10 07:43:05

此PEP提出从每个字节码指令添加映射到生成它们的行的STARTAND结束列偏移。此数据将用于改善CPython解释器显示的回溯,以便提高调试体验。 PEP还提出添加API,允许其他overtools(例如覆盖分析工具,分析器,示踪剂,调试器)从代码对象中的信息。

此PEP的主要动机是提高呈现关于错误的反馈,以帮助调试。

Python目前将字节码映射到编译中的行号。解释器使用此映射指向与an错误相关的源行。虽然这种线级粒度有用,但Python代码的Asingle行可以编译成数十个字节码操作,难以跟踪导致错误的哪一部分。

回溯(最近呼叫最后):文件" test.py",第2行,在<模块> x [' a'] [' b'] [' c'] [' d'] = 1typeerror:' noneType&# 39;对象不是预测的

从回溯中,无法确定一个导致错误的none元素的词典。用户通常必须附加adebugger或分开他们的表达以追踪问题。

但是,如果解释器映射到列偏移作为云线号的列偏移,则可以帮助显示:

回溯(最近呼叫最后):文件" test.py",第2行,在<模块> x [' a'] [' b'] [' c'] [' d'] = 1 ^^^^^^^^ ^^^^^^^^^^ typeerror:' none型'对象不是预测的

向用户指示对象x [' a'] [' b']一定是没有。这是在回溯中的每个帧都会出现突出显示。例如,IFA类似的错误是复杂函数呼叫链的一部分,追溯将显示与每个帧中的当前指令相关联的代码:

回溯(最近呼叫最后):文件" test.py",第14行,在<模块> lel3(x)^^^^^^ ^文件" test.py",第12行,在lel3返回lel2(x)/ 23 ^^^^^^^^文件" test.py& #34;,第9行,在LEL2返回25 + leel(x)+ lel(x)^^^^^^文件" test.py",第6行,在lel返回1 + foo(a ,b,c = x [' z'] [' x'] [' y'] ['] [&#39 ; y'],d = e)^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^ TypeRor:' noneType'对象不是预测的

将多个对象传递到函数调用时,在它们中拨打相同的属性。对于实例,此错误:

回溯(最近呼叫最后):文件" test.py",第19行,在< module> foo(a.name,b.name,c.name)attributeError:' noneType'对象没有属性'姓名'

回溯(最近呼叫最后):文件" test.py",第17行,在<模块> foo(a.name,b.name,c.name)^^^^^^ ativinguterError:' noneType'对象没有属性'姓名'

在处理具有复杂数学表达式的行时,尤其是库如Numpy,其中算法过程可以基于参数失败。例如:

回溯(最近呼叫最后):文件" test.py",第1行,在<模块> x =(a + b)@(c + d)valueerror:操作数不能与形状一起广播(1,2)(2,3)

没有明确的指示如何失败,它是左侧的左侧,右侧还是矩阵乘法?使用此页面,新的错误消息将如下所示:

回溯(最近呼叫最后):文件" test.py",第1行,在<模块> x =(a + b)@(c + d)^^^^^ valueError:操作数不能与形状一起广播(1,2)(2,3)

抛开,此额外信息对CodeCoverage工具也很有用,使它们能够测量表达式级别覆盖,而不是仅限于行级覆盖范围。例如,考虑到以下行:

覆盖范围,配置文件或状态分析工具将突出显示在BlicBranches中的全线,使得无法区分分支机构。这是Pycoverage中的已知问题[3]。

在其他语言中发生了类似的努力,例如Java Inthe形式[1]。 Java中的NullPointerexceptions在具有复杂表达式的行进时类似地模糊不清。 nullpointerexception将提供很少的帮助查找错误的根本原因。 Jep358的主机展示相当复杂,需要使用控制流程图分析仪和反作用技术转移到导致空指针的源代码来返回通过ThebyTecode。虽然此解决方案的复杂性高,但需要每个TimeJava字节码进行分解器的维护,但这种改进被认为是仅为仅为一个异常类型提供的ExtTra信息值得的。

为了识别出experficeare介绍时执行的源代码范围,此提议需要为每个字节德引导添加新数据。这将对磁盘上的PYC文件的大小和内存中的代码对象大小的影响。此提议的作者以康乐的数据类型,以试图减少这种影响。对于每个字节码指令,BucationoveHepe将存储两个UINT8_T(一个用于开始偏移的一个用于偏移量)。

作为衡量这种变化的影响的说明性示例,我们已经大规则,这种变化将使标准图书馆的文件的大小从22%(6MB)从28.4MB增加到34.7MB。内存使用的开销将相同(假设完整的标准库加载到剪辑中)。我们认为这是一个非常可接受的数字,因为开销的秩序非常小,特别是考虑了现代计算机的故事和记忆功能。此外,在Python程序的一般主题大小中,Python程序不是由代码对象的主导。要检查该组织,我们已经执行了几个流行的Pypi项目的测试套件(包括Numpy,Pytest,Django和Cython)以及多个应用程序(黑色,Pylint,Mypy在MyPy或标准库上执行),并且我们将代码对象表示通常是计划平均内存大小的3-6%。

我们了解到,这些信息的额外费用可能不可接受的用户,因此我们提出了一种在&#34中执行Python; Opt-2"优化模式(Python -OO),它将导致PYC文件不包括额外信息。

为了有足够的信息来正确地解析出现错误的处于唤醒的处于唤醒状态的位置,需要一种连接字节码指令和COLUMN偏移(开始和结束偏移)的地图。这在时尚的线号中类似于当前链接到Bytecode指令。

偏移信息将通过名为co_col_offsets中的thecode对象类中的新属性将偏移信息暴露于python,该属性将返回序列的OftWo-Element元组(包含开始偏移和结束偏移),或者如果没有偏移信息创建代码对象。

将添加两个新的C-API函数,pycode_addr2startoffset和pycode_addr2endoffset,其可以分别获取START和ENDOFFSETS给定BYTECODE指令的索引。如果不可用偏移信息,则返回0。

用于代码对象的新私有(下划线前缀)C-API构造函数将添加包含一个字节对象,其中包含偶数位置中的偶数偏移中的开始偏移量和奇数位置的结束偏移。旧构造函数将FEEREFT UNTOUCHED兼容以后兼容性,并将创建代码ObjectSwithout新字段。

这些偏移量由编译器传播,该编译器从存储当前inall AST节点存储。它们是1索引的,值为0将意味着该信息不可用。虽然AST节点使用int类型来存储值,但UINT8_T类型将用于存储在新的地图中的存储影响。该决策允许偏移到0到25​​5,而大于这些值的偏移将被视为缺失(值0)。我们认为这是一个可接受的折衷,因为Python中的线长倾向于低于此限制(查询PYPI的前100个套餐小于0.01%的线的时间超过255个字符)。

保持当前行为,只会显示一个单线的内部背面。有关跨越多行的说明(结束偏移和尾部偏移属于不同的行),结束偏移将设置为0(意味着它不可用)。如果启动偏移量不是0,则显示代码会被interpletEd,好像范围跨越来自行驶速度到行的末尾。无法计算实际的结束偏移,因为编译器不知道有多少个字符“行”实际上代表了多少个字符。

显示回溯时,将修改默认异常钩子从代码对象修改ToQuery此信息,并使用它在回溯中显示每个显示的行的序列,如果信息是可见的。例如:

文件" test.py",第6行,在lek返回1 + foo(a,b,c = x [' z'] [' x'] [ ' y'] [' z'] [' y'],d = e)^^^^^^^^^^^^^^^^^^^ ^^^^^ TypeError:' noneType'对象不是预测的

在显示回溯时,指令偏移将从TheTraceback对象中获取。这使得突出显示在工作地重新培养的例外情况,而无需将新信息存储在堆栈中。 forexample,对于此代码:

def foo(x):1 + 1/0 + 2def bar(x):try:1 + foo(x)+ foo(x)除例外除外,e:light $ error(" oh no!" )来自EBAR(酒吧(2)))

回溯(最近呼叫最后):文件" test.py",第6行,在栏1 + foo(x)+ foo(x)^^^^^^文件" test.py& #34;,第2行,在foo 1 + 1/0 + 2 ^^^ zerodivisionError:按Zerothe划分的例外是以下例外的直接原因:回溯(最近呼叫最后):文件"测试。 PY",第10行,在<模块>酒吧(酒吧(栏(2)))^^^^^^文件" test.py",第8行,在条例中," oh no!"""") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^ ^^^^^ ^ ^ ^^^^ ^ ^ ^^^^ ^ ^ ^^^^ ^ ^ ^^^ ^ ^ ^^^ valeserror:哦不

def foo(x):1 + 1/0 + 2def bar(x):尝试:1 + foo(x)+ foo(x)除例外除外:rivebar(bar(bar(2))))

回溯(最近呼叫最后):文件" test.py",第10行,在<模块>酒吧(酒吧(bar(2)))^^^^^^文件" test.py",第6行,在bar 1 + foo(x)+ foo(x)^^^^^^文件" test.py",第2行,在foo 1 + 1/0 + 2 ^^^ zerodivisionerror:division your for

为那些关心存储和制作开销的用户提供退出机制,当Python在&#34执行Python时,该功能将与ExtraInAlation一起停用.OPT-2"优化模式(Python -OO)导致PYC文件没有与extararyQuired数据相​​关的开销。

允许第三方工具和当前解析托管的其他程序以追赶并允许用户停用新功能,将提供假期的方法以取消激活显示新的突出小标集(但不避免存储数据,用户需要使用Python" Opt-2"优化模式):

这些标志将在Python解释器的下一个版本中删除(从释放此功能的版本计数)。

某些指令可以跨越多条线,因此结束偏移并启动偏移可以位于两个不同的线路中。我们已经决定将启动偏移的值TOSET为正确的值,并设置0的值0 TOTE END偏移。这将导致从起始偏移量的值开始突出显示整个行。这一决定背后的原因是终点线将要求我们存储类似于CO_LNOTAB的另一个字段,但我们的回溯机械仅突出显示单个策划框架,因此只能使用此信息来决定突出显示该行。另一方面,端线对于诸如覆盖量测量工具和示踪剂的诸如诸如覆盖量的工具和示踪剂有用。

具有配置标志,即使在执行Pythonin非优化模式时也可以听起来可以听起来可以听起来可能会导致用与激活的标志编译的解释器版本创建的Pyc文件创建的PYC文件。这可能导致崩溃,令人难以调试普通用户,并将在彼此之间进行不同的PYC FileSinpatible。由于这种PYC可以作为没有原始来源的纤维化或应用程序的一部分运送,它也不总是强制重新编译所述PYC文件。由于这些原因,我们有助于使用-o标志来选择此行为。

虽然可以在代码对象中实现某种形式的压缩,但是代码对象中的新数据,我们认为这是由于其较大影响(在PYC文件的情况下)而不是这一提议的曲线镜,而我们由于它们中的缺少缺陷(如果在代码对象中的新数据)中,期望校准偏移不会良好压缩。

感谢Carl Friedrich Bolz-Tereick-Tereick用于显示Pypy解释器的初始原型,并有用讨论。

本文档放在公共领域或在TheCC0-1.0-Universal License下,以允许更为允许。 来源:https://github.com/python/peps/blob/master/pep-0657.rst.