从 python 中的 FrameInfo 或框架获取函数签名
get function signature from FrameInfo or frame in python
在 python 中编写我自己的异常挂钩时,我想到了使用 inspect
模块来为自己提供有关如何调用该函数的更多信息。
这意味着函数的签名以及传递给它的参数。
import inspect
frame_infos = inspect.trace() # get the FrameInfos
for f_idx, f_info in enumerate(frame_infos):
frame_dict.update(f_info.frame.f_locals) # update namespace with deeper frame levels
#Output basic Error-Information
print(f' File "{f_info.filename}", line {f_info.lineno}, in {f_info.function}')
for line in f_info.code_context:
print(f' {line.strip()}')
########################################################
# show signature and arguments 1 level deeper
if f_idx+1 < len(frame_infos):
func_name = frame_infos[f_idx+1].function #name of the function
try:
func_ref = frame_dict[func_name] # look up in namespace
sig = inspect.signature(func_ref) # call signature for function_reference
except KeyError: sig = '(signature unknown)'
print(f' {func_name} {sig}\n')
print(f' {frame_infos[f_idx+1].frame.f_locals}\n')
这对于像这样的基本示例非常有效:
def test1 ( x: int, y: tuple = 0 )->list: # the types obviously dont match
return test2(y, b=x, help=0)
def test2 ( a, *args, b, **kwargs ):
return a + b / 0
try:
test1(5)
except: ...
输出:
File "C:/test/errorHandler.py", line 136, in <module>
test1(5)
test1 (x:int, y:tuple=0) -> list
{'y': 0, 'x': 5}
File "C:/test/errorHandler.py", line 130, in test1
return test2(y, b=x, help=0)
test2 (a, *args, b, **kwargs)
{'kwargs': {'help': 0}, 'args': (), 'b': 5, 'a': 0}
File "C:/test/errorHandler.py", line 133, in test2
return a + b / 0
但是,一旦您离开 1 个文件,您就无法将函数名称映射到基本命名空间。
file1: import file2; try: file2.foo() except: ...
file2: import file3; def foo(): file3.foo()
file3: def foo(): return 0/0
非常重要,我正在寻找一种从 FrameInfo
或 frame
对象中获取函数(如 <function foo at 0x000002F4A43ACD08>
)的方法,但我看到的唯一信息是名称和文件和行。
(我不喜欢通过在特定行查看源文件来获取签名的想法。)
迄今为止最好的参考是 Inspect
-documentation,但我还没有找到有用的东西。
在this answer by jsbueno的基础上,找到了恢复签名的方案
使用gc
(垃圾收集器)功能get_referrers()
您可以搜索直接引用特定对象的所有对象。
使用框架 f_code
提供的代码对象,您可以使用此函数来查找框架本身以及函数。
code_obj = frame.f_code
import gc #garbage collector
print(gc.get_referrers(code_obj))
# [<function foo at 0x0000020F758F4EA0>, <frame object at 0x0000020F75618CF8>]
所以,找到真正的函数就大功告成了:
# find the object that has __code__ and is actally the object with that specific code
[obj for obj in garbage_collector.get_referrers(code_obj)
if hasattr(obj, '__code__')
and obj.__code__ is code_obj][0]
现在您可以对过滤后的对象使用 inspect.signature()
。
来自 gc.get_referrers(objs)
的免责声明:
This function will only locate those containers which support garbage collection; extension types which do refer to other objects but do not support garbage collection will not be found.
完整代码示例:
import inspect
import gc
def ERROR_Printer_Inspection ( stream = sys.stderr ) :
"""
called in try: except: <here>
prints the last error-traceback in the given "stream"
includes signature and function arguments if possible
"""
stream.write('Traceback (most recent call last):\n')
etype, value, _ = sys.exc_info() # get type and value for last line of output
frame_infos = inspect.trace() # get frames for source-lines and arguments
for f_idx, f_info in enumerate(frame_infos):
stream.write(f' File "{f_info.filename}", line {f_info.lineno}, in {f_info.function}\n')
for line in f_info.code_context: # print location and code parts
stream.write(f' {line.lstrip()}')
if f_idx+1 < len(frame_infos): # signature and arguments
code_obj = frame_infos[f_idx+1].frame.f_code # codeobject from next frame
function_obj = [obj for obj in gc.get_referrers(code_obj) if hasattr(obj, '__code__') and obj.__code__ is code_obj]
if function_obj: # found some matching object
function_obj=function_obj[0] # function_object
func_name = frame_infos[f_idx + 1].function # name
stream.write(f' > {func_name} {inspect.signature(function_obj)}\n')
next_frame_locals = frame_infos[f_idx+1].frame.f_locals # calling arguments
# filter them to the "calling"-arguments
arguments = dict((key, next_frame_locals[key]) for key in code_obj.co_varnames if key in next_frame_locals.keys())
stream.write(f' -> {str(arguments)[1:-1]}\n')
stream.write(f'{etype.__name__}: {value}\n')
stream.flush()
问题:
显示 "calling" 参数如果在函数启动后被编辑,可能会产生误导:
def foo (a, b, **kwargs):
del a, kwargs
b = 'fail'
return 0/0
try: foo(0, 1, test=True)
except: ERROR_Printer_Inspection()
输出:
Traceback (most recent call last):
File "C:/test/errorHandler.py", line 142, in <module>
try: foo(0, 1, test=True)
> foo (a, b, **kwargs)
-> 'b': 'fail'
File "C:/test/errorHandler.py", line 140, in foo
return 0 / 0
ZeroDivisionError: division by zero
你不能相信,但这是另一个问题的问题。
链接:
这里有一些链接,如果你想自己研究的话:
- Getting the Python function for a code object
- Python-Docs: inspect-module
- Python-Docs: Data model
- Python-Docs: garbage collector
- late.am - Exploring Python Code Objects
在 python 中编写我自己的异常挂钩时,我想到了使用 inspect
模块来为自己提供有关如何调用该函数的更多信息。
这意味着函数的签名以及传递给它的参数。
import inspect
frame_infos = inspect.trace() # get the FrameInfos
for f_idx, f_info in enumerate(frame_infos):
frame_dict.update(f_info.frame.f_locals) # update namespace with deeper frame levels
#Output basic Error-Information
print(f' File "{f_info.filename}", line {f_info.lineno}, in {f_info.function}')
for line in f_info.code_context:
print(f' {line.strip()}')
########################################################
# show signature and arguments 1 level deeper
if f_idx+1 < len(frame_infos):
func_name = frame_infos[f_idx+1].function #name of the function
try:
func_ref = frame_dict[func_name] # look up in namespace
sig = inspect.signature(func_ref) # call signature for function_reference
except KeyError: sig = '(signature unknown)'
print(f' {func_name} {sig}\n')
print(f' {frame_infos[f_idx+1].frame.f_locals}\n')
这对于像这样的基本示例非常有效:
def test1 ( x: int, y: tuple = 0 )->list: # the types obviously dont match
return test2(y, b=x, help=0)
def test2 ( a, *args, b, **kwargs ):
return a + b / 0
try:
test1(5)
except: ...
输出:
File "C:/test/errorHandler.py", line 136, in <module>
test1(5)
test1 (x:int, y:tuple=0) -> list
{'y': 0, 'x': 5}
File "C:/test/errorHandler.py", line 130, in test1
return test2(y, b=x, help=0)
test2 (a, *args, b, **kwargs)
{'kwargs': {'help': 0}, 'args': (), 'b': 5, 'a': 0}
File "C:/test/errorHandler.py", line 133, in test2
return a + b / 0
但是,一旦您离开 1 个文件,您就无法将函数名称映射到基本命名空间。
file1:
import file2; try: file2.foo() except: ...
file2:import file3; def foo(): file3.foo()
file3:def foo(): return 0/0
非常重要,我正在寻找一种从 FrameInfo
或 frame
对象中获取函数(如 <function foo at 0x000002F4A43ACD08>
)的方法,但我看到的唯一信息是名称和文件和行。
(我不喜欢通过在特定行查看源文件来获取签名的想法。)
迄今为止最好的参考是 Inspect
-documentation,但我还没有找到有用的东西。
在this answer by jsbueno的基础上,找到了恢复签名的方案
使用gc
(垃圾收集器)功能get_referrers()
您可以搜索直接引用特定对象的所有对象。
使用框架 f_code
提供的代码对象,您可以使用此函数来查找框架本身以及函数。
code_obj = frame.f_code
import gc #garbage collector
print(gc.get_referrers(code_obj))
# [<function foo at 0x0000020F758F4EA0>, <frame object at 0x0000020F75618CF8>]
所以,找到真正的函数就大功告成了:
# find the object that has __code__ and is actally the object with that specific code
[obj for obj in garbage_collector.get_referrers(code_obj)
if hasattr(obj, '__code__')
and obj.__code__ is code_obj][0]
现在您可以对过滤后的对象使用 inspect.signature()
。
来自 gc.get_referrers(objs)
的免责声明:
This function will only locate those containers which support garbage collection; extension types which do refer to other objects but do not support garbage collection will not be found.
完整代码示例:
import inspect
import gc
def ERROR_Printer_Inspection ( stream = sys.stderr ) :
"""
called in try: except: <here>
prints the last error-traceback in the given "stream"
includes signature and function arguments if possible
"""
stream.write('Traceback (most recent call last):\n')
etype, value, _ = sys.exc_info() # get type and value for last line of output
frame_infos = inspect.trace() # get frames for source-lines and arguments
for f_idx, f_info in enumerate(frame_infos):
stream.write(f' File "{f_info.filename}", line {f_info.lineno}, in {f_info.function}\n')
for line in f_info.code_context: # print location and code parts
stream.write(f' {line.lstrip()}')
if f_idx+1 < len(frame_infos): # signature and arguments
code_obj = frame_infos[f_idx+1].frame.f_code # codeobject from next frame
function_obj = [obj for obj in gc.get_referrers(code_obj) if hasattr(obj, '__code__') and obj.__code__ is code_obj]
if function_obj: # found some matching object
function_obj=function_obj[0] # function_object
func_name = frame_infos[f_idx + 1].function # name
stream.write(f' > {func_name} {inspect.signature(function_obj)}\n')
next_frame_locals = frame_infos[f_idx+1].frame.f_locals # calling arguments
# filter them to the "calling"-arguments
arguments = dict((key, next_frame_locals[key]) for key in code_obj.co_varnames if key in next_frame_locals.keys())
stream.write(f' -> {str(arguments)[1:-1]}\n')
stream.write(f'{etype.__name__}: {value}\n')
stream.flush()
问题:
显示 "calling" 参数如果在函数启动后被编辑,可能会产生误导:
def foo (a, b, **kwargs):
del a, kwargs
b = 'fail'
return 0/0
try: foo(0, 1, test=True)
except: ERROR_Printer_Inspection()
输出:
Traceback (most recent call last):
File "C:/test/errorHandler.py", line 142, in <module>
try: foo(0, 1, test=True)
> foo (a, b, **kwargs)
-> 'b': 'fail'
File "C:/test/errorHandler.py", line 140, in foo
return 0 / 0
ZeroDivisionError: division by zero
你不能相信,但这是另一个问题的问题。
链接:
这里有一些链接,如果你想自己研究的话:
- Getting the Python function for a code object
- Python-Docs: inspect-module
- Python-Docs: Data model
- Python-Docs: garbage collector
- late.am - Exploring Python Code Objects