在 Python 中获取中间值?

Get Intermediate Value in Python?

我正在尝试编写一个 Python sys.excepthook,它除了打印出您编写的代码的堆栈跟踪外,还打印出 repr每个评估值。

例如,如果我运行下面的代码:

def greeting():
    return 'Hello'

def name():
    return

greeting() + name()

而不只是打印出来:

Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
        greeting() + name()
TypeError: cannot concatenate 'str' and 'NoneType' objects

它还会打印出 'Hello' + None 这样我就可以立即看到哪个值无效并知道要查看的代码的正确区域(显然这是一个非常简单的示例)。

我知道 CPU 需要将这些中间值存储在一些临时寄存器中...我怀疑 Python 内部必须做类似的事情,我希望有一些方法我可以访问这些临时值,可能是通过 inspect 模块或类似的东西。

使用 pythonic try/except 块:

g = greeting()
n = name()
try:
    g + n
except:
    raise ValueError('g: %s, n: %s' % (g, n))

对于@LukasGraf,"proper Python coding practices"上的阅读清单:

调用 sys.exceptionhook() 时,您无法再获得这些中间值,因为它们已经消失了。是的,组件表达式的中间结果存储在某个地方 Python。您当时无法直接访问 'somewhere',发生异常时也根本不会保留这些内容。

在 CPython 中,标准 Python 实现,即 'somewhere' 是附加到当前执行帧的 堆栈 (每个主动功能有一个)。 Python 代码被编译为 字节码 ,然后计算循环执行该字节码,字节码中的 individual bytecode instructions 对该堆栈进行操作。

您可以使用 dis.dis() function 查看示例表达式使用的字节码:

>>> import dis
>>> dis.dis("greeting() + name()")
  1           0 LOAD_NAME                0 (greeting)
              2 CALL_FUNCTION            0
              4 LOAD_NAME                1 (name)
              6 CALL_FUNCTION            0
              8 BINARY_ADD
             10 RETURN_VALUE

然后查看那些字节码指令的作用:

  • LOAD_NAME 0 找到名为 greeting 的对象并将其放在堆栈顶部 (TOS)。
  • CALL_FUNCTION 0 从堆栈中删除 0 个元素作为调用的参数,然后从堆栈中取出下一个对象作为可调用对象,使用参数调用该对象,并将结果作为新服务条款。
  • BINARY_ADD取出栈顶的两个元素,将它们相加,并将结果放回TOS。

因此,LOAD_NAMECALL_FUNCTION 一起执行对命名对象的调用,堆栈顶部最终引用两个结果,name() 结果位于greeting() 结果。 BINARY_ADD 指令然后将堆栈中的这两个结果替换为将它们加在一起的结果。

您无法从 Python 中访问该堆栈,因为正是执行 Python 字节码的行为首先使 Python 工作。任何 可以 访问堆栈的代码都必须处理堆栈当前正用于执行该 Python 代码的事实!

但是你有一个更大的问题。如果您查看 CPython 源代码,您可以在 ceval.c 中的评估循环中搜索指令名称。当您查看 BINARY_ADD instruction implementation 时,您可以看到两个输入值在 将它们加在一起之前 从堆栈中移除:

TARGET(BINARY_ADD) {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *sum;
    // code to set sum as the result of addibg left to right
    SET_TOP(sum);
    if (sum == NULL)
        goto error;
    DISPATCH();
}

如果 BINARY_ADD 因异常而失败,则 sum == NULL 为真并执行 goto error 以结束调用堆栈并将异常传播到第一个 try阻止或失败,最终调用 sys.excepthook() 函数。那时,中间结果从堆栈中消失了。上面块中的局部 rightleft 指针也很长,早已不复存在(C 使用块作用域,当执行 goto error 时,作用域退出,因此变量丢失)。