trace — Seguir el flujo del programa

Propósito:Supervisar qué declaraciones y funciones se ejecutan a medida que se ejecuta un programa para producir cobertura e información de gráficos de llamadas.

El módulo trace es útil para comprender la forma en que se ejecuta un programa. Observa las declaraciones ejecutadas, produce informes de cobertura y ayuda a investigar las relaciones entre funciones que se llaman entre sí.

Programa de ejemplo

Este programa se usará en los ejemplos en el resto de la sección. Importa otro módulo llamado recurse y luego ejecuta una función desde él.

trace_example/main.py
from recurse import recurse


def main():
    print('This is the main program.')
    recurse(2)


if __name__ == '__main__':
    main()

La función recurse() se invoca hasta que el argumento de nivel alcanza 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.')

Seguimiento de ejecución

Es fácil usar trace directamente desde la línea de comando. Las declaraciones que se ejecutan mientras se ejecuta el programa se imprimen cuando se da la opción --trace. Este ejemplo también ignora la ubicación de la biblioteca estándar de Python para evitar el rastreo en importlib y otros módulos que podrían ser más interesantes en otro ejemplo, pero que abarrotan la salida en este simple ejemplo.

$ python3 -m trace --ignore-dir=.../lib/python3.6 \
--trace trace_example/main.py

 --- modulename: main, funcname: <module>
main.py(7): """
main.py(10): from recurse import recurse
 --- modulename: recurse, funcname: <module>
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:
 --- modulename: trace, funcname: _unsettrace
trace.py(77):         sys.settrace(None)

La primera parte de la salida muestra las operaciones de configuración realizadas por trace. El resto de la salida muestra la entrada en cada función, incluido el módulo donde se encuentra la función, y luego las líneas del archivo fuente a medida que se ejecutan. recurse() se ingresa tres veces, como se esperaba según la forma en que se llama en main().

Cobertura de código

Ejecutar trace desde la línea de comando con la opción --count producirá información de informe de cobertura de código, que detalla qué líneas se ejecutan y cuáles se omiten. Dado que un programa complejo generalmente está compuesto por múltiples archivos, se genera un informe de cobertura separado para cada uno. Por defecto, los archivos del informe de cobertura se escriben en el mismo directorio que el módulo, con el nombre del módulo pero con una extensión .cover en lugar de .py.

$ python3 -m trace --count trace_example/main.py

This is the main program.
recurse(2)
recurse(1)
recurse(0)

Se producen dos archivos de salida, 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:     return
       
    1: if __name__ == '__main__':
    1:     main()

y trace_example/recurse.cover:

trace_example/recurse.cover
       
    1: def recurse(level):
    3:     print 'recurse(%s)' % level
    3:     if level:
    2:         recurse(level-1)
    3:     return
       
    1: def not_called():
           print 'This function is never called.'

Nota

Aunque la línea def recurse (level): tiene un recuento de 1, eso no significa que la función solo se ejecutó una vez. Significa que la definición de la función solo se ejecutó una vez. Lo mismo se aplica a def not_called():, porque la definición de la función se evalúa a pesar de que la función en sí nunca se llama.

También es posible ejecutar el programa varias veces, quizás con diferentes opciones, para guardar los datos de cobertura y producir un informe combinado. La primera vez que trace se ejecuta con un archivo de salida, informa de un error cuando intenta cargar cualquier dato existente para fusionarse con los nuevos resultados antes de crear el archivo.

$ 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

Para generar informes una vez que la información de cobertura se grabe en los archivos .cover, usa la opción --report.

$ python3 -m trace --coverdir coverdir1 --report --summary \
--missing --file coverdir1/coverage_report.dat \
trace_example/main.py

lines   cov%   module   (path)
  461     0%   trace   (.../lib/python3.6/trace.py)
    7   100%   trace_example.main   (trace_example/main.py)
    7    85%   trace_example.recurse
(trace_example/recurse.py)

Como el programa se ejecutó tres veces, el informe de cobertura muestra valores tres veces más altos que el primer informe. La opción --summary agrega el porcentaje de información cubierta a la salida. El módulo recurse solo está cubierto en un 87%. Mirando el archivo de cover para recurse muestra que el cuerpo de not_called nunca se ejecuta, indicado por el prefijo >>>>>>.

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.')

Relaciones entre las funciones

Además de la información de cobertura, trace recopilará e informará sobre las relaciones entre funciones que se llaman entre sí.

Para obtener una lista simple de las funciones llamadas, usa --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: .../lib/python3.6/trace.py, modulename: trace,
funcname: _unsettrace
filename: trace_example/main.py, modulename: main, funcname:
<module>
filename: trace_example/main.py, modulename: main, funcname:
main
filename: trace_example/recurse.py, modulename: recurse,
funcname: <module>
filename: trace_example/recurse.py, modulename: recurse,
funcname: recurse

Para obtener más detalles sobre quién realiza la llamada, usa --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.6/trace.py ***
    trace.Trace.runctx -> trace._unsettrace
  --> trace_example/main.py
    trace.Trace.runctx -> main.<module>

  --> trace_example/recurse.py


*** trace_example/main.py ***
    main.<module> -> main.main
  --> trace_example/recurse.py
    main.main -> recurse.recurse

*** trace_example/recurse.py ***
    recurse.recurse -> recurse.recurse

Nota

Ni --listfuncs ni --trackcalls respetan los argumentos --ignore-dirs o --ignore-mods, por lo que parte de la salida de este ejemplo se elimina utilizando grep en su lugar.

Interfaz de programación

Para un mayor control sobre la interfaz trace, se puede invocar desde un programa utilizando un objeto Trace. Trace admite la configuración de dispositivos y otras dependencias antes de ejecutar una sola función o ejecutar un comando de Python para rastrear.

trace_run.py
import trace
from trace_example.recurse import recurse

tracer = trace.Trace(count=False, trace=True)
tracer.run('recurse(2)')

Dado que el ejemplo solo se rastrea en la función recurse(), no se incluye información de main.py en la salida.

$ python3 trace_run.py

 --- modulename: trace_run, funcname: <module>
<string>(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:
 --- modulename: trace, funcname: _unsettrace
trace.py(77):         sys.settrace(None)

Esa misma salida se puede producir con el método runfunc() también.

trace_runfunc.py
import trace
from trace_example.recurse import recurse

tracer = trace.Trace(count=False, trace=True)
tracer.runfunc(recurse, 2)

runfunc() acepta argumentos posicionales arbitrarios y argumentos con palabras clave, que se pasan a la función cuando el trazador la invoca.

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

Guardar datos de resultados

Los recuentos y la información de cobertura también se pueden registrar, al igual que con la interfaz de línea de comandos. Los datos deben guardarse explícitamente, utilizando la instancia CoverageResults del objeto Trace.

trace_CoverageResults.py
import trace
from trace_example.recurse import recurse

tracer = trace.Trace(count=True, trace=False)
tracer.runfunc(recurse, 2)

results = tracer.results()
results.write_results(coverdir='coverdir2')

Este ejemplo guarda los resultados de la cobertura en el directorio coverdir2.

$ python3 trace_CoverageResults.py

recurse(2)
recurse(1)
recurse(0)

$ find coverdir2

coverdir2
coverdir2/trace_example.recurse.cover

El archivo de salida contiene

       #!/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.')

Para guardar los datos de recuento para generar informes, use los argumentos infile y outfile para Trace.

trace_report.py
import trace
from trace_example.recurse import recurse

tracer = trace.Trace(count=True,
                     trace=False,
                     outfile='trace_report.dat')
tracer.runfunc(recurse, 2)

report_tracer = trace.Trace(count=False,
                            trace=False,
                            infile='trace_report.dat')
results = tracer.results()
results.write_results(summary=True, coverdir='/tmp')

Pasa un nombre de archivo a infile para leer los datos almacenados previamente, y un nombre de archivo a outfile para escribir nuevos resultados después del seguimiento. Si infile y outfile son iguales, tiene el efecto de actualizar el archivo con datos acumulativos.

$ python3 trace_report.py

recurse(2)
recurse(1)
recurse(0)
lines   cov%   module   (path)
    7    42%   trace_example.recurse
(.../trace_example/recurse.py)

Opciones

El constructor de Trace toma varios parámetros opcionales para controlar el comportamiento del tiempo de ejecución.

count
Booleano. Activa el recuento de números de línea. Por defecto es verdadero.
countfuncs
Booleano. Activa la lista de funciones llamadas durante la ejecución. Por defecto es falso.
countcallers
Booleano. Activa el seguimiento de funciones llamadoras y llamadas. Por defecto es falso.
ignoremods
Secuencia. Lista de módulos o paquetes a ignorar al rastrear la cobertura. El valor predeterminado es una tupla vacía.
ignoredirs
Secuencia. Lista de directorios que contienen módulos o paquetes a ignorar. El valor predeterminado es una tupla vacía.
infile
Nombre del archivo que contiene los valores de recuento en caché. Por defecto es None.
outfile
Nombre del archivo a utilizar para almacenar archivos de conteo en caché. El valor predeterminado es None y los datos no se almacenan.

Ver también