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.
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
.
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
:
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
:
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
>>>>>>
.
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.
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.
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
.
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
.
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
- Documentación de la biblioteca estándar para trace
- Seguimiento de un programa mientras se ejecuta – El módulo
sys
incluye funciones para agregar una función de rastreo personalizada al intérprete en tiempo de ejecución. - coverage.py – Módulo converage de Ned Batchelder.
- figleaf – Aplicación de cobertura de Titus Brown.