Рубрики
Без рубрики

трассировка – следовать потоку программы

Автор оригинала: 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

Имя файла, который будет использоваться для хранения кэшированных файлов подсчета. По умолчанию Нет, и данные не сохраняются.

Смотрите также