получить подпись функции из FrameInfo или frame в python - PullRequest
0 голосов
/ 09 октября 2018

При написании моего собственного обработчика исключений в python, я пришел к мысли использовать inspect -модуль, чтобы предоставить myselfe больше информации о том, как вызывается функция.

Это означает подпись функции, а также аргументы , переданные ей.

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: ...

output:

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

так важно, я ищу способ получить функцию (например, <function foo at 0x000002F4A43ACD08>) из FrameInfo илиframe -объект, но единственная информация, которую я вижу, это имя, файл и строка.
(Мне не нравится идея получить подпись, посмотрев в исходный файл на определенную строку.)

Лучшим справочным документом до сих пор была Inspect -документация ,но я еще не нашел что-то полезное.

1 Ответ

0 голосов
/ 11 октября 2018

На основе этого ответа от 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() на фильтруемойobject.


Disclamer от gc.get_referrers(objs):

Эта функция будет определять местоположение только тех контейнеров, которые поддерживают сборку мусора;типы расширений, которые ссылаются на другие объекты, но не поддерживают сборку мусора, не будут найдены.


пример полного кода:

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()

проблемы:

отображение «вызывающих» аргументов может привести к ошибкам, если они были отредактированы после запуска функции:

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

вы можетене верьте этому, но это проблема другого вопроса.


ссылки:

Вот несколько ссылок, если вы хотите исследовать для себя:

...