pol*_*tex 5 python debugging introspection python-3.x
出于教育目的,我希望能够打印当前函数的完整调用表达式。不一定来自异常处理程序。
经过一番研究,我最终得到了这段非常简单的代码:
import inspect
import linecache
def print_callexp(*args, **kwargs):
try:
frame = inspect.currentframe()
# method 1, using inspect module only
print(inspect.getframeinfo(frame.f_back).code_context)
# method 2, just for the heck of it
linecache.checkcache(frame.f_code.co_filename)
line = linecache.getline(
frame.f_back.f_code.co_filename,
frame.f_back.f_lineno,
frame.f_back.f_globals)
print(line)
# there's probably a similar method with traceback as well
except:
print("Omagad")
a_var = "but"
print_callexp(a_var, "why?!", 345, hello="world")
Run Code Online (Sandbox Code Playgroud)
结果:
[' print_callexp(a_var, "why?!", 345, hello="world")\n']
print_callexp(a_var, "why?!", 345, hello="world")
Run Code Online (Sandbox Code Playgroud)
只要调用表达式位于一行上,它就完全符合我的要求。但是对于多行表达式,它只会得到最后一行,显然需要我进一步挖掘调用上下文。
# same example but with a multiple lines call
a_var = "but"
print_callexp(
a_var, "why?!", 345, hello="world")
Run Code Online (Sandbox Code Playgroud)
这给了我们:
[' a_var, "why?!", 345, hello="world")\n']
a_var, "why?!", 345, hello="world")
Run Code Online (Sandbox Code Playgroud)
如何正确打印完整的调用表达式?
“使用 lineno 值并应用一些正则表达式/评估技巧”不是一个可接受的答案。我更喜欢更干净、能用的东西。我不介意导入更多模块,只要它们是 Python 3.x 标准库的一部分即可。但尽管如此,我会对任何参考资料感兴趣。
出于好奇,这是我出于这种非生产性目的的最终工作代码。乐趣无处不在!(几乎)
我不会立即将此标记为已接受的答案,希望有人能在不久的将来为我们提供更好的选择......
它按预期提取整个调用表达式。此代码假定调用表达式是一个裸函数调用,没有任何魔法、特殊技巧或嵌套/递归调用。这些特殊情况显然会使检测部分变得不那么琐碎,并且无论如何都是偏离主题的。
具体来说,我使用当前函数名称来帮助定位调用表达式的 AST 节点,以及作为inspect
起点提供的行号。
我无法用来inspect.getsource()
隔离调用者的块,这本来可以更加优化,因为我发现它返回不完整的源代码的情况。例如,当调用者的代码直接位于main的作用域中时。不知道这应该是一个错误还是一个功能......
一旦我们有了源代码,我们只需要ast.parse()
获取根 AST 节点并遍历树即可找到对当前函数的最新调用,瞧!
#!/usr/bin/env python3
import inspect
import ast
def print_callexp(*args, **kwargs):
def _find_caller_node(root_node, func_name, last_lineno):
# init search state
found_node = None
lineno = 0
def _luke_astwalker(parent):
nonlocal found_node
nonlocal lineno
for child in ast.iter_child_nodes(parent):
# break if we passed the last line
if hasattr(child, "lineno"):
lineno = child.lineno
if lineno > last_lineno:
break
# is it our candidate?
if (isinstance(child, ast.Name)
and isinstance(parent, ast.Call)
and child.id == func_name):
# we have a candidate, but continue to walk the tree
# in case there's another one following. we can safely
# break here because the current node is a Name
found_node = parent
break
# walk through children nodes, if any
_luke_astwalker(child)
# dig recursively to find caller's node
_luke_astwalker(root_node)
return found_node
# get some info from 'inspect'
frame = inspect.currentframe()
backf = frame.f_back
this_func_name = frame.f_code.co_name
# get the source code of caller's module
# note that we have to reload the entire module file since the
# inspect.getsource() function doesn't work in some cases (i.e.: returned
# source content was incomplete... Why?!).
# --> is inspect.getsource broken???
# source = inspect.getsource(backf.f_code)
#source = inspect.getsource(backf.f_code)
with open(backf.f_code.co_filename, "r") as f:
source = f.read()
# get the ast node of caller's module
# we don't need to use ast.increment_lineno() since we've loaded the whole
# module
ast_root = ast.parse(source, backf.f_code.co_filename)
#ast.increment_lineno(ast_root, backf.f_code.co_firstlineno - 1)
# find caller's ast node
caller_node = _find_caller_node(ast_root, this_func_name, backf.f_lineno)
# now, if caller's node has been found, we have the first line and the last
# line of the caller's source
if caller_node:
#start_index = caller_node.lineno - backf.f_code.co_firstlineno
#end_index = backf.f_lineno - backf.f_code.co_firstlineno + 1
print("Hoooray! Found it!")
start_index = caller_node.lineno - 1
end_index = backf.f_lineno
lineno = caller_node.lineno
for ln in source.splitlines()[start_index:end_index]:
print(" {:04d} {}".format(lineno, ln))
lineno += 1
def main():
a_var = "but"
print_callexp(
a_var, "why?!",
345, (1, 2, 3), hello="world")
if __name__ == "__main__":
main()
Run Code Online (Sandbox Code Playgroud)
你应该得到这样的东西:
Hoooray! Found it!
0079 print_callexp(
0080 a_var, "why?!",
0081 345, (1, 2, 3), hello="world")
Run Code Online (Sandbox Code Playgroud)
它仍然感觉有点混乱,但是哦,这是一个非常不寻常的目标。至少在 Python 中看起来很不寻常。例如,乍一看,我希望找到一种方法来直接访问已加载的 AST 节点,该节点可以通过inspect
框架对象或以类似的方式提供服务,而不必手动创建新的 AST 节点。
请注意,我完全不知道这是否是 CPython 特定代码。不应该是这样。至少从我从文档中读到的内容来看。
另外,我想知道为什么模块(或作为辅助模块)中没有官方的漂亮打印功能。可能会使用附加参数来完成这项工作,以允许格式化输出并更轻松地进行调试。ast
ast.dump()
indent
AST
附带说明一下,我发现这个非常简洁的函数可以帮助使用 AST。
归档时间: |
|
查看次数: |
1554 次 |
最近记录: |