我正在尝试编写一个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)
模块或类似的东西。
答案 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_NAME
和CALL_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编码实践”的阅读清单: