Как получить выражение вызова при трассировке функции Python?

когда внутри функции трассировки, отлаживая вызов функции, можно каким-то образом получить вызывающее выражение?

Я могу получить номер вызывающей строки из объекта traceback, но если есть несколько вызовов функций (возможно, к одной и той же функции) в этой строке (например. как подвыражение в большем выражении), то как я мог узнать, откуда пришел этот вызов? Я был бы счастлив даже со смещением от начала исходной строки.

traceback.tb_lasti кажется, дает более granual context (индекс последнего байт-кода) - возможно ли каким-то образом подключить байт-код к его точному исходному диапазону?

EDIT: просто чтобы уточнить -- мне нужно извлечь конкретное (sub)выражение (callsite) из вызывающей исходной строки.

4 ответов


traceback кадры имеют номер строки тоже:

lineno = traceback.tb_lineno

вы также можете добраться до объекта кода, который будет иметь имя и имя файла:

name = traceback.tb_frame.f_code.co_name
filename = traceback.tb_frame.f_code.co_filename

вы можете использовать имя файла и номер строки, плюс их рамы и linecache модуль чтобы эффективно превратить это в правильную строку исходного кода:

linecache.checkcache(filename)
line = linecache.getline(filename, lineno, traceback.tb_frame.f_globals)

это traceback модуль использует, чтобы превратить traceback в полезную информацию, в любом случай.

поскольку байт-код имеет только номер строки, связанный с ним, вы не можете напрямую привести в байт-код обратно к точному часть строки исходного кода; вам нужно будет самостоятельно проанализировать эту строку, чтобы определить, какой байт-код будет излучать каждая часть, а затем сопоставить его с байт-кодом объекта кода.

вы могли бы сделать это с ast модуль, но вы не можете сделать это по строкам, так как вам понадобится контекст области например, для создания правильных байт-кодов для поиска локальных или глобальных имен.


к сожалению, скомпилированный байт-код потерял смещения столбцов; индекс байт-кода для отображения номера строки содержится в co_lnotab таблица номеров строк. The dis модуль-хороший способ взглянуть на байт-код и интерпретировать co_lnotab:

>>> dis.dis(compile('a, b, c', '', 'eval'))
  1           0 LOAD_NAME                0 (a)
              3 LOAD_NAME                1 (b)
              6 LOAD_NAME                2 (c)
              9 BUILD_TUPLE              3
             12 RETURN_VALUE        
  ^-- line number

однако ничто не мешает нам возиться с номером строки:

>>> a = ast.parse('a, b, c', mode='eval')
>>> for n in ast.walk(a):
...     if hasattr(n, 'col_offset'):
...         n.lineno = n.lineno * 1000 + n.col_offset
>>> dis.dis(compile(a, '', 'eval'))
1000           0 LOAD_NAME                0 (a)

1003           3 LOAD_NAME                1 (b)

1006           6 LOAD_NAME                2 (c)
              9 BUILD_TUPLE              3
             12 RETURN_VALUE        

С момента компиляции кода напрямую должны быть таким же, как компиляция через ast.parse, и так как возиться с номера строк не стоит влияет на сгенерированный байт-код, (кроме co_lnotab), вы должны быть в состоянии:

  • найдите исходный файл
  • разберите его с ast.parse
  • munge номера строк в ast, чтобы включить смещения столбцов
  • скомпилировать ast
  • использовать tb_lasti для поиска munged co_lnotab
  • преобразовать номер строки munged обратно в (номер строки, столбец смещение)

Я знаю, что это некромантия, но я опубликовал аналогичный вопрос вчера, не увидев этого сначала. Поэтому на всякий случай, если кому-то интересно, я решил свою проблему иначе, чем принятый ответ, используя inspect и ast модули в Python3. Это все еще для отладки и образовательных целей, но это делает трюк.

ответ довольно длинный, поэтому вот ссылка


вот как я, наконец, решил проблему: я инструментировал каждый вызов функции в исходной программе, обернув его в вызов вспомогательной функции вместе с информацией об исходном местоположении исходного вызова. На самом деле мне было интересно контролировать оценку каждого подвыражения в программе, поэтому я завернул каждое подвыражение.

точнее: когда у меня было выражение e в оригинальной программе, он стал

_after(_before(location_info), e)

в инструментальная программа. Помощники были определены следующим образом:

def _before(location_info):
    return location_info

def _after(location_info, value):
    return value

когда tracer сообщил о вызове _before, Я знал, что он собирается оценить выражение в месте, представленном location_info (трассировка система дает мне доступ к локальным переменным/параметрам, вот как я узнал значение location_info). Когда tracer сообщил о вызове _after, Я знал, что expession указано location_info только что был оценен, и значение находится в value.

я мог бы записал выполнение " обработка событий "прямо в эти вспомогательные функции и полностью обошел систему трассировки, но мне это было нужно и по другим причинам, поэтому я использовал эти помощники только для запуска события" вызова " в системе трассировки.

результат можно увидеть здесь:http://thonny.org