Как получить выражение вызова при трассировке функции 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
для поиска mungedco_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