Автор оригинала: Doug Hellmann.
Цель:
Следите за тем, какие операторы и функции выполняются как программа запускается для получения информации о покрытии и графике вызовов.
Модуль trace
полезен для понимания того, как работает программа. Он наблюдает за выполненными операторами, создает отчеты о покрытии и помогает исследовать отношения между функциями, которые вызывают друг друга.
Пример программы
Эта программа будет использоваться в примерах в оставшейся части раздела. Он импортирует другой модуль с именем recurse
, а затем запускает из него функцию.
trace_example/main.py
from recurse import recurse def main(): print('This is the main program.') recurse(2) if __name__ '__main__': main()
Функция recurse ()
вызывает себя, пока аргумент уровня не достигнет 0
.
trace_example/recurse.py
def recurse(level): print('recurse({})'.format(level)) if level: recurse(level - 1) def not_called(): print('This function is never called.')
Отслеживание исполнения
Легко использовать trace
прямо из командной строки. Операторы, выполняемые по мере выполнения программы, печатаются, если задана опция --trace
. В этом примере также игнорируется расположение стандартной библиотеки Python, чтобы избежать трассировки в importlib и другие модули, которые могут быть более интересны в другом примере, но загромождают вывод в этом простом примере.
$ python3 -m trace --trace trace_example/main.py --- modulename: main, funcname:main.py(7): """ main.py(10): from recurse import recurse --- modulename: recurse, funcname: recurse.py(7): """ recurse.py(11): def recurse(level): recurse.py(17): def not_called(): main.py(13): def main(): main.py(18): if __name__ == '__main__': main.py(19): main() --- modulename: main, funcname: main main.py(14): print('This is the main program.') This is the main program. main.py(15): recurse(2) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(2) recurse.py(13): if level: recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(1) recurse.py(13): if level: recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(0) recurse.py(13): if level:
В первой части вывода показаны операции настройки, выполняемые trace
. Остальная часть выходных данных показывает запись в каждую функцию, включая модуль, в котором расположена функция, а затем строки исходного файла по мере их выполнения. recurse ()
вводится три раза, как и ожидалось, в зависимости от способа его вызова в main ()
.
Покрытие кода
Запуск trace
из командной строки с параметром --count
приведет к созданию информации отчета о покрытии кода с подробным описанием того, какие строки выполняются, а какие пропускаются. Поскольку сложная программа обычно состоит из нескольких файлов, для каждого создается отдельный отчет о покрытии. По умолчанию файлы отчета о покрытии записываются в тот же каталог, что и модуль, названный в честь модуля, но с расширением .cover
вместо .py
.
$ python3 -m trace --count trace_example/main.py This is the main program. recurse(2) recurse(1) recurse(0)
Создается два выходных файла, trace_example/main.cover
:
trace_example/main.cover
1: from recurse import recurse 1: def main(): 1: print('This is the main program.') 1: recurse(2) 1: if __name__ '__main__': 1: main()
и trace_example/recurse.cover
:
trace_example/recurse.cover
1: def recurse(level): 3: print('recurse({})'.format(level)) 3: if level: 2: recurse(level - 1) 1: def not_called(): print('This function is never called.')
Примечание
Хотя строка def recurse (level):
имеет счетчик 1
, это не означает, что функция была запущена только один раз. Это означает, что функция определение была выполнена только один раз. То же самое относится к def not_called ():
, потому что определение функции вычисляется, даже если сама функция никогда не вызывается.
Также можно запустить программу несколько раз, возможно, с разными параметрами, чтобы сохранить данные покрытия и создать комбинированный отчет. При первом запуске trace
с выходным файлом он сообщает об ошибке, когда пытается загрузить какие-либо существующие данные для объединения с новыми результатами перед созданием файла.
$ python3 -m trace --coverdir coverdir1 --count \ --file coverdir1/coverage_report.dat trace_example/main.py This is the main program. recurse(2) recurse(1) recurse(0) Skipping counts file 'coverdir1/coverage_report.dat': [Errno 2] No such file or directory: 'coverdir1/coverage_report.dat' $ python3 -m trace --coverdir coverdir1 --count \ --file coverdir1/coverage_report.dat trace_example/main.py This is the main program. recurse(2) recurse(1) recurse(0) $ python3 -m trace --coverdir coverdir1 --count \ --file coverdir1/coverage_report.dat trace_example/main.py This is the main program. recurse(2) recurse(1) recurse(0) $ ls coverdir1 coverage_report.dat main.cover recurse.cover
Для создания отчетов после записи информации о покрытии в файлы .cover
используйте параметр --report
.
$ python3 -m trace --coverdir coverdir1 --report --summary \ --missing --file coverdir1/coverage_report.dat \ trace_example/main.py lines cov% module (path) 7 100% trace_example.main (trace_example/main.py) 7 85% trace_example.recurse (trace_example/recurse.py)
Поскольку программа запускалась трижды, отчет о покрытии показывает значения в три раза выше, чем первый отчет. Параметр --summary
добавляет к выходным данным информацию о процентном покрытии. Модуль recurse
покрыт только 87%. Просмотр файла обложки для recurse
показывает, что тело not_called
действительно никогда не запускается, на что указывает префикс >>>>>>
.
coverdir1/trace_example.recurse.cover
3: def recurse(level): 9: print('recurse({})'.format(level)) 9: if level: 6: recurse(level - 1) 3: def not_called(): >>>>>> print('This function is never called.')
Вызов отношений
Помимо информации о покрытии, trace
будет собирать и сообщать о взаимосвязях между функциями, которые вызывают друг друга.
Чтобы просмотреть простой список вызываемых функций, используйте --listfuncs
.
$ python3 -m trace --listfuncs trace_example/main.py | \ grep -v importlib This is the main program. recurse(2) recurse(1) recurse(0) functions called: filename: trace_example/main.py, modulename: main, funcname:filename: trace_example/main.py, modulename: main, funcname: main filename: trace_example/recurse.py, modulename: recurse, funcname: filename: trace_example/recurse.py, modulename: recurse, funcname: recurse
Для получения дополнительных сведений о том, кто звонит, используйте --trackcalls
.
$ python3 -m trace --listfuncs --trackcalls \ trace_example/main.py | grep -v importlib This is the main program. recurse(2) recurse(1) recurse(0) calling relationships: *** .../lib/python3.7/trace.py *** --> trace_example/main.py trace.Trace.runctx -> main.--> trace_example/recurse.py *** trace_example/main.py *** main. -> main.main --> trace_example/recurse.py main.main -> recurse.recurse *** trace_example/recurse.py *** recurse.recurse -> recurse.recurse
Примечание
Ни --listfuncs
, ни --trackcalls
не учитывают аргументы --ignore-dirs
или --ignore-mods
. , поэтому часть вывода из этого примера удаляется с помощью grep
.
Интерфейс программирования
Для большего контроля над интерфейсом trace
его можно вызвать из программы с помощью объекта Trace
. Trace
поддерживает настройку фикстур и других зависимостей перед запуском отдельной функции или выполнением отслеживаемой команды Python.
trace_run.py
import trace from trace_example.recurse import recurse tracer trace.Trace(countFalse, traceTrue) tracer.run('recurse(2)')
Поскольку в примере отслеживается только функция recurse ()
, информация из main.py
не включается в вывод.
$ python3 trace_run.py --- modulename: trace_run, funcname:(1): --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(2) recurse.py(13): if level: recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(1) recurse.py(13): if level: recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(0) recurse.py(13): if level:
Такой же результат можно получить с помощью метода runfunc ()
.
trace_runfunc.py
import trace from trace_example.recurse import recurse tracer trace.Trace(countFalse, traceTrue) tracer.runfunc(recurse, 2)
runfunc ()
принимает произвольные позиционные аргументы и аргументы ключевого слова, которые передаются функции, когда она вызывается трассировщиком.
$ python3 trace_runfunc.py --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(2) recurse.py(13): if level: recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(1) recurse.py(13): if level: recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(12): print('recurse({})'.format(level)) recurse(0) recurse.py(13): if level:
Сохранение данных результатов
Счетчики и информация о покрытии также могут быть записаны, как и в интерфейсе командной строки. Данные должны быть сохранены явно с использованием экземпляра CoverageResults
из объекта Trace
.
trace_CoverageResults.py
import trace from trace_example.recurse import recurse tracer trace.Trace(countTrue, traceFalse) tracer.runfunc(recurse, 2) results tracer.results() results.write_results(coverdir'coverdir2')
В этом примере результаты покрытия сохраняются в каталог coverdir2
.
$ python3 trace_CoverageResults.py recurse(2) recurse(1) recurse(0) $ find coverdir2 coverdir2 coverdir2/trace_example.recurse.cover
Выходной файл содержит
#!/usr/bin/env python # encoding: utf-8 # # Copyright (c) 2008 Doug Hellmann All rights reserved. # """ >>>>>> """ #end_pymotw_header >>>>>> def recurse(level): 3: print('recurse({})'.format(level)) 3: if level: 2: recurse(level - 1) >>>>>> def not_called(): >>>>>> print('This function is never called.')
Чтобы сохранить данные подсчета для создания отчетов, используйте аргументы infile
и outfile
для Trace
.
trace_report.py
import trace from trace_example.recurse import recurse tracer trace.Trace(countTrue, traceFalse, outfile'trace_report.dat') tracer.runfunc(recurse, 2) report_tracer trace.Trace(countFalse, traceFalse, infile'trace_report.dat') results tracer.results() results.write_results(summaryTrue, coverdir'/tmp')
Передайте имя файла в infile
для чтения ранее сохраненных данных и имя файла в outfile
для записи новых результатов после трассировки. Если infile
и outfile
совпадают, это приводит к обновлению файла совокупными данными.
$ python3 trace_report.py recurse(2) recurse(1) recurse(0) lines cov% module (path) 7 42% trace_example.recurse (.../trace_example/recurse.py)
Опции
Конструктор Trace
принимает несколько дополнительных параметров для управления поведением во время выполнения.
считать
Булево. Включает подсчет номеров строк. По умолчанию True.
countfuncs
Булево. Включает список функций, вызываемых во время выполнения. По умолчанию False.
счетчик
Булево. Включает отслеживание звонящих и вызываемых. По умолчанию Ложь.
ignoremods
Последовательность. Список модулей или пакетов, которые следует игнорировать при отслеживании покрытие. По умолчанию пустой кортеж.
игнорируемые
Последовательность. Список каталогов, содержащих модули или пакеты, которые будут игнорируется. По умолчанию пустой кортеж.
infile
Имя файла, содержащего кешированные значения счетчика. По умолчанию Нет.
Outfile
Имя файла, который будет использоваться для хранения кэшированных файлов подсчета. По умолчанию Нет, и данные не сохраняются.
Смотрите также
- стандартная библиотечная документация для трассировки
- Отслеживание программы во время ее выполнения – модуль
sys
включает средства для добавления настраиваемой функции трассировки в интерпретатор во время выполнения. - extension.py – модуль покрытия Неда Батчелдера.
- figleaf – приложение Титуса Брауна для покрытия.