在Python中获得中级值?

时间:2015-05-27 22:16:51

标签: python exception-handling python-internals

我正在尝试编写一个Python 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

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

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

2 个答案:

答案 0 :(得分:3)

在调用sys.exceptionhook()之前,您再也无法获得这些中间值,因为它们已经消失了。是的,组件表达式的中间结果由Python存储在某个位置。您当时无法直接访问该“某处”,也无法在发生异常时将其保留在任何地方。

在CPython中,是标准的Python实现,“某处”是附加到当前执行框架的 stack (每个活动函数都有一个)。将Python代码编译为 bytecode ,然后执行评估循环执行该字节码,然后字节码中的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()结果位于{{ 1}}个结果。然后,greeting()指令将这两个结果替换为堆栈中的结果。

您无权从Python内部访问该堆栈,因为执行Python字节码实际上是使Python首先工作的行为。任何可以访问堆栈的代码都必须处理以下事实:当前正在使用堆栈来执行该Python代码!

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

ceval.c

如果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()right指针也很久很久了(C使用块作用域,并且当执行left时会退出作用域,因此变量将丢失)。

答案 1 :(得分:-3)

使用pythonic try/except块:

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

对于@LukasGraf,一个关于“正确的Python编码实践”的阅读清单: