在 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_NAME
和 CALL_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()
函数。那时,中间结果从堆栈中消失了。上面块中的局部 right
和 left
指针也很长,早已不复存在(C 使用块作用域,当执行 goto error
时,作用域退出,因此变量丢失)。
我正在尝试编写一个 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_NAME
和 CALL_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()
函数。那时,中间结果从堆栈中消失了。上面块中的局部 right
和 left
指针也很长,早已不复存在(C 使用块作用域,当执行 goto error
时,作用域退出,因此变量丢失)。