trace - Seguire il Flusso del Programma
Scopo: Controlla quali istruzioni e funzioni sono eseguite quando il programma è in esecuzione per produrre informazioni di copertura e grafi di chiamata.
Il modulo trace è utile per comprendere il modo nel quale un programma viene eseguito. Osserva le istruzioni eseguite, produce report di copertura e aiuta a investigare le relazioni tra funzioni che si chiamano le une con le altre.
Programma di esempio
Questo programma verrà usato negli esempi che seguono. Importa un altro modulo chiamato recurse
e da esso esegue una funzione.
# esempio_trace/main.py
from recurse import recurse
def main():
print('Questo è il programma principale.')
recurse(2)
if __name__ == '__main__':
main()
La funzione recurse()
chiama se stessa fino a che l'argomento level
raggiunge 0.
# esempio_trace/recurse.py
def recurse(level):
print('recurse({})'.format(level))
if level:
recurse(level - 1)
def not_called():
print('Questa funzione non viene mai chiamata.')
Tracciare l'Esecuzione
E' facile usare trace direttamente dalla riga di comando. Le istruzioni che vengono eseguite mentre il programma gira vengono stampate se viene fornita l'opzione --trace
. Questo esempio ignora anche la locazione della libreria standard di Python (riferita all'interprete Python utilizzato - in questo caso /usr/lib/python3.8
- n.d.t.) per evitare la tracciatura di importlib e altri moduli che potrebbero essere più interessanti da includere in un altro esempio, ma che in questo caso farebbero solo confusione nella stampa dei risultati.
$ python3 -m trace --ignore-dir=/usr/lib/python3.8 --trace esempio_trace/main.py --- modulename: main, funcname: <module> main.py(3): from recurse import recurse --- modulename: recurse, funcname: <module> recurse.py(3): def recurse(level): recurse.py(9): def not_called(): main.py(6): def main(): main.py(11): if __name__ == '__main__': main.py(12): main() --- modulename: main, funcname: main main.py(7): print('Questo è il programma principale.') Questo è il programma principale. main.py(8): recurse(2) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(2) recurse.py(5): if level: recurse.py(6): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(1) recurse.py(5): if level: recurse.py(6): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(0) recurse.py(5): if level:
La prima parte del risultato mostra le operazioni di impostazione effettuate da trace
. Il resto mostra l'entrata in ciascuna funzione, compreso il modulo dove la funzione risiede, quindi le righe del file sorgente così come sono eseguite. recurse()
viene eseguita tre volte, come ci si attende in base al modo di chiamata in main()
.
Copertura del Codice
Eseguendo trace
da riga di comando con l'opzione --count
verranno prodotte informazioni di copertura di codice, dettagliando quali righe sono state eseguite e quali sono state saltate. Visto che un programma complesso in genere è composto da diversi file, viene prodotto un report di copertura separato per ciascuno. Nella modalità predefinita i file di report sono scritti nella stessa directory del modulo, attribuendogli il nome del modulo con estensione .cover
invece che .py
.
$ python3 -m trace --count esempio_trace/main.py Questo è il programma principale. recurse(2) recurse(1) recurse(0)
Sono prodotti due file in output, esempio_trace/main.cover
:
# esempio_trace/main.cover
1: from recurse import recurse
1: def main():
1: print('Questo è il programma principale.')
1: recurse(2)
1: if __name__ == '__main__':
1: main()
ed esempio_trace/recurse.cover
.
# esempio_trace/recurse.cover
1: def recurse(level):
3: print('recurse({})'.format(level))
3: if level:
2: recurse(level - 1)
1: def not_called():
print('Questa funzione non viene mai chiamata.')
recurse(level):
abbia un conteggio di 1, non significa che quella funzione è stata eseguita una sola volta. Significa che la definizione della funzione è stata eseguita una volta. Lo stesso vale per not_called():
visto che la definizione della funzione è comunque valutata anche se la stessa non viene mai chiamata.E' anche possibile eseguire il programma diverse volte, forse con diverse opzioni, per salvare i dati di copertura e produrre un report combinato. La prima volta che trace
viene eseguito con un file in uscita, riporta un errore quando tenta di caricare dati esistenti da combinare con i nuovi risultati prima di creare il file.
$ python3 -m trace --coverdir coverdir1 --count --file coverdir1/coverage_report.dat esempio_trace/main.py Questo è il programma principale. 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 esempio_trace/main.py Questo è il programma principale. recurse(2) recurse(1) recurse(0)
$ python3 -m trace --coverdir coverdir1 --count --file coverdir1/coverage_report.dat esempio_trace/main.py Questo è il programma principale. recurse(2) recurse(1) recurse(0)
$ ls coverdir1 coverage_report.dat esempio_trace.main.cover esempio_trace.recurse.cover main.cover recurse.cover
Per produrre report una volta che le informazioni di copertura sono registrate nei file .cover
si usi l'opzione --report
.
$ python3 -m trace --coverdir coverdir1 --report --summary --missing --file coverdir1/coverage_report.dat esempio_trace/main.py lines cov% module (path) 6 100% esempio_trace.main (esempio_trace/main.py) 6 83% esempio_trace.recurse (esempio_trace/recurse.py)
Visto che il programma viene eseguito tre volte, il report di copertura mostra valori tre volte più alti del primo report. L'opzione --summary
aggiunge la percentuale di informazioni coperte al risultato. Il modulo recurse
è coperto solo per l'83%. Guardando il file .cover per recurse
si nota che il corpo della funzione not_called()
in effetti non è mai eseguito, indicato dal prefisso >>>>>>
.
# coverdir1/esempio_trace.recurse.cover
3: def recurse(level):
9: print('recurse({})'.format(level))
9: if level:
6: recurse(level - 1)
3: def not_called():
>>>>>> print('Questa funzione non viene mai chiamata.')
Relazioni tra Chiamate
Oltre alle informazioni di copertura, trace
raccoglie e riporta circa le relazioni tra funzioni che si chiamano le une con le altre.
Per una semplice lista delle funzioni chiamate si usi --listfuncs
.
$ python3 -m trace --listfuncs esempio_trace/main.py | grep -v importlib Questo è il programma principale. recurse(2) recurse(1) recurse(0) functions called: filename: <frozen zipimport>, modulename: <frozen zipimport>, funcname: zipimporter.__init__ filename: esempio_trace/main.py, modulename: main, funcname: <module> filename: esempio_trace/main.py, modulename: main, funcname: main filename: esempio_trace/recurse.py, modulename: recurse, funcname: <module> filename: esempio_trace/recurse.py, modulename: recurse, funcname: recurse
Per maggiori dettagli circa chi effettua la chiamata si usi -trackcalls
.
$ python3 -m trace --listfuncs --trackcalls esempio_trace/main.py | grep -v importlib Questo è il programma principale. recurse(2) recurse(1) recurse(0) calling relationships: *** /usr/lib/python3.8/trace.py *** --> esempio_trace/main.py trace.Trace.runctx -> main.<module> --> esempio_trace/recurse.py --> <frozen zipimport> *** <frozen zipimport> *** *** esempio_trace/main.py *** main.<module> -> main.main --> esempio_trace/recurse.py main.main -> recurse.recurse *** esempio_trace/recurse.py *** recurse.recurse -> recurse.recurse
--listfuncs
nè --trackcalls
onorano gli argomenti --ignore-dirs
o --ignore-mods
, quindi la parte di risultato da questo esempio è estratta usando grep
.Interfaccia di Programmazione
Per un maggior controllo sulla sua interfaccia, trace
può essere chiamato da un altro programma tramite un oggetto Trace
. Trace
supporta l'impostazione di impianti e altre dipendenze prima di eseguire una singola funzione o un comando Python da tracciare.
# trace_run.py
import trace
from esempio_trace.recurse import recurse
tracer = trace.Trace(count=False, trace=True)
tracer.run('recurse(2)')
Visto che l'esempio traccia solo la funzione recurse()
non sono incluse nel risultato le informazioni da main.py
.
$ python3 trace_run.py --- modulename: trace_run, funcname: <module> <string>(1): --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(2) recurse.py(5): if level: recurse.py(6): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(1) recurse.py(5): if level: recurse.py(6): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(0) recurse.py(5): if level:
Lo stesso risultato può essere ottenuto anche con il metodo runfunc()
.
# trace_runfunc.py
import trace
from esempio_trace.recurse import recurse
tracer = trace.Trace(count=False, trace=True)
tracer.runfunc(recurse, 2)
runfunc()
accetta argomenti arbitrari posizionali e nominativi, che sono passati alla funzione quando è chiamata dal tracciatore.
$ python3 trace_runfunc.py --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(2) recurse.py(5): if level: recurse.py(6): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(1) recurse.py(5): if level: recurse.py(6): recurse(level - 1) --- modulename: recurse, funcname: recurse recurse.py(4): print('recurse({})'.format(level)) recurse(0) recurse.py(5): if level:
Salvare i Dati del Risultato
Anche le informazioni di conteggio e copertura possono essere registrate, proprio come nella interfaccia da riga di comando. I dati devono essere salvati esplicitamente, usando l'istanza CoverageResults
dall'oggetto Trace
.
# trace_CoverageResults.py
import trace
from esempio_trace.recurse import recurse
tracer = trace.Trace(count=True, trace=False)
tracer.runfunc(recurse, 2)
results = tracer.results()
results.write_results(coverdir='coverdir2')
Questo esempio salva i risultati di copertura nella directory coverdir2
.
$ python3 trace_CoverageResults.py recurse(2) recurse(1) recurse(0)
$ find coverdir2 coverdir2 coverdir2/esempio_trace.recurse.cover
Il file in uscita contiene.
# coverdir2/esempio_trace.recurse.cover
>>>>>> def recurse(level):
3: print('recurse({})'.format(level))
3: if level:
2: recurse(level - 1)
>>>>>> def not_called():
>>>>>> print('Questa funzione non viene mai chiamata.')
Per salvare i dati di conteggio per generare report, si usino gli argomenti infile
e outfile
di Trace
.
# trace_report.py
import trace
from esempio_trace.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')
Si passi un nome di file ad infile
per leggere dati salvati in precedenza, e un nome di file ad outfile
per scrivere i nuovi risultati dopo la tracciatura. Se infile
ed outfile
sono uguali, si crea l'effetto di aggiornare il file con dati cumulativi.
$ python3 trace_report.py recurse(2) recurse(1) recurse(0) lines cov% module (path) 6 50% esempio_trace.recurse (.../esempio_trace/recurse.py)
Opzioni
Il costruttore di Trace
riceve parecchi parametri opzionali per controllare il comportamento in fase di esecuzione.
PARAMETRO | TIPO | DESCRIZIONE |
---|---|---|
count |
Booleano | Attiva il conteggio del numero di riga. Predefinito True |
countfuncs |
Booleano | Attiva l'elenco delle funzioni chiamate in fase di esecuzione. Predefinito False |
countcallers |
Booleano | Attiva la tracciatura di chiamanti e chiamati. Predefinito False |
ignoremods |
Sequenza | Lista di moduli e pacchetti da ignorare nella tracciatura della copertura. Predefinito una tupla vuota |
ignoredirs |
Sequenza | Lista di directory contenenti moduli e pacchetti da ignorare. Predefinito una tupla vuota |
infile |
Stringa | Nome del file contenente i valori di conteggio in cache. Predefinito None |
outfile |
Stringa | Nome del file da usare per conservare i file di conteggio. Predefinito None e i dati non sono conservati. |
Vedere anche:
- trace
- La documentazione della libreria standard per questo modulo.
- Tracciare un programma in fase di esecuzione
- Il modulo sys include servizi per aggiungere una funzione di tracciatura personalizzata all'interprete in fase di esecuzione (in corso di traduzione)
- coverage.py
- Il modulo coverage di Neil Batchelder.
- figleaf
- L'applicazione di copertura di Titus Brown