Scopo | Monitora quali istruzioni e funzioni sono eseguite mentre un programma è in esecuzione per produrre informazioni di code coverage e grafici di chiamata |
Versione Python | 2.3 e superiore |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo trace
Il modulo trace aiuta nel comprendere in che modo viene eseguito un proprio programma. Si possono tracciare le istruzioni eseguite, produrre rapporti di coverage, ed investigare sulle relazioni tra le funzioni che si chiamano vicendevolmente.
E' facile usare trace direttamente dalla riga di comando. Dati i seguenti script Python come input:
from recurse import recurse
def main():
print 'Questo è il programma principale.'
recurse(2)
return
if __name__ == '__main__':
main()
def recurse(level):
print 'ricorsione(%s)' % level
if level:
recurse(level-1)
return
def not_called():
print 'Questa funzione non è mai chiamata.'
Si può vedere quali istruzioni sono esegute mentre il programma è in esecuzione usando l'opzione --trace .
$ python -m trace --trace trace_example/main.py --- modulename: threading, funcname: settrace threading.py(90): _trace_hook = func --- modulename: main, funcname:main.py(4): from recurse import recurse --- modulename: recurse, funcname: recurse.py(4): def recurse(level): recurse.py(10): 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(5): print 'ricorsione(%s)' % level ricorsione(2) recurse.py(6): if level: recurse.py(7): recurse(level-1) --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(1) recurse.py(6): if level: recurse.py(7): recurse(level-1) --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(0) recurse.py(6): if level: recurse.py(8): return recurse.py(8): return recurse.py(8): return main.py(9): return
La prima parte del risultato mostra alcune operazioni di impostazione eseguite da
trace
. Il resto del risultato mostra l'entrata in ogni funzione, compreso il modulo dove la funzione si trova, quindi le righe del file sorgente, mentre vengono eseguite. Si può notare che in
recurse()
si entra per tre volte, proprio come ci si aspetta, visto come è stata chiamata in
main()
.
L'esecuzione di trace da riga di comando con l'opzione --count produrrà un rapporto informativo di code coverage , in modo che si possa vedere quali righe sono state eseguite e quali sono state saltate. Visto che un programma in genere è composto da file multipli l'informativa viene prodotta per ognuno. In modalità predefinita i file contenenti l'informativa sono scritti nella stessa directory del modulo, e si chiamano con il nome del modulo, ma con una estensione .cover in luogo di .py
$ python -m trace --count trace_example/main.py Questo è il programma principale. ricorsione(2) ricorsione(1) ricorsione(0)
Ecco i due file .cover, trace_example/main.cove :
1: from recurse import recurse 1: def main(): 1: print 'Questo è il programma principale.' 1: recurse(2) 1: return 1: if __name__ == '__main__': 1: main()
e trace_example/recurse.cove :
1: def recurse(level): 3: print 'ricorsione(%s)' % level 3: if level: 2: recurse(level-1) 3: return 1: def not_called(): print 'Questa funzione non è mai chiamata.'
E' anche possibile eseguire il programma diverse volte, magari con opzioni differenti, per salvare i dati di coverage e produrre un rapporto combinato.
$ python -m trace --coverdir coverdir1 --count --file coverdir1/coverage_report.dat trace_example/main.py Questo è il programma principale. ricorsione(2) ricorsione(1) ricorsione(0) Skipping counts file 'coverdir1/coverage_report.dat': [Errno 2] No such file or directory: 'coverdir1/coverage_report.dat' $ python -m trace --coverdir coverdir1 --count --file coverdir1/coverage\ > _report.dat trace_example/main.py Questo è il programma principale. ricorsione(2) ricorsione(1) ricorsione(0) $ python -m trace --coverdir coverdir1 --count --file coverdir1/coverage\ > _report.dat trace_example/main.py Questo è il programma principale. ricorsione(2) ricorsione(1) ricorsione(0)
Una volta che l'informativa di coverage è stata registrata nei file .cover , si possono produrre dei rapporti con l'opzione --report
$ python -m trace --coverdir coverdir1 --report --summary --missing --file coverdir1/coverage_report.dat trace_example/main.py lines cov% module (path) 607 0% threading (/usr/lib/python2.6/threading.py) 7 100% trace_example.main (trace_example/main.py) 7 85% trace_example.recurse (trace_example/recurse.py)
Visto che il programma viene eseguito tre volte, il rapporto di coverage mostra valori tre volte più alti rispetto al primo rapporto. L'opzione --summary aggiunge la percentuale di informazioni di coverage (il codice coperto) al risultato di cui sopra. Il modulo recurse ha una percentuale di copertura (coverage) del solo 87%. Una rapida occhiata al file .cover per recurse mostra che il corpo di not_called() non viene in effetti mai eseguito, indicato dal prefisso >>>>>> .
Oltre alle informazioni di coverage, trace raccoglie e riporta le relazioni tra le funzioni che si chiamano l'una con l'altra.
Per una semplice lista delle funzioni chiamate si usa --listfuncs :
£ python -m trace --listfuncs trace_example/main.py Questo è il programma principale. ricorsione(2) ricorsione(1) ricorsione(0) functions called: filename: /usr/lib/python2.6/threading.py, modulename: threading, funcname: settrace 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
Per maggiori dettagli su chi sta eseguendo la chiamata si usa --trackcalls .
$ python -m trace --listfuncs --trackcalls trace_example/main.py Questo è il programma principale. ricorsione(2) ricorsione(1) ricorsione(0) calling relationships: *** /usr/lib/python2.6/trace.py *** --> /usr/lib/python2.6/threading.py trace.Trace.runctx -> threading.settrace --> trace_example/main.py trace.Trace.runctx -> main.*** trace_example/main.py *** main. -> main.main --> trace_example/recurse.py main. -> recurse. main.main -> recurse.recurse *** trace_example/recurse.py *** recurse.recurse -> recurse.recurse
Per un maggiore controllo sull'interfaccia di trace, si può chiamare all'interno del proprio programma usando un oggetto
Trace
, che consente di impostare degli impianti ed altre dipendenze prima di eseguire una singola funzione od un comando Python da tracciare.
import trace
from trace_example.recurse import recurse
tracer = trace.Trace(count=False, trace=True)
tracer.run('recurse(2)')
Visto che l'esempio traccia solo all'interno della funzione
recurse()
, nel risultato non sono incluse le informazioni circa
main.py
$ python trace_run.py --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(2) recurse.py(6): if level: recurse.py(7): recurse(level-1) --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(1) recurse.py(6): if level: recurse.py(7): recurse(level-1) --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(0) recurse.py(6): if level: recurse.py(8): return recurse.py(8): return recurse.py(8): return
Si sarebbe potuto produrre lo stesso risultato anche con il metodo
runfunc()
. Questo metodo accetta parametri posizionali e con parola chiave, i quali sono passati alla funzione quanto viene chiamata dal tracciatore.
import trace
from trace_example.recurse import recurse
tracer = trace.Trace(count=False, trace=True)
tracer.runfunc(recurse, 2)
$ python trace_runfunc.py --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(2) recurse.py(6): if level: recurse.py(7): recurse(level-1) --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(1) recurse.py(6): if level: recurse.py(7): recurse(level-1) --- modulename: recurse, funcname: recurse recurse.py(5): print 'ricorsione(%s)' % level ricorsione(0) recurse.py(6): if level: recurse.py(8): return recurse.py(8): return recurse.py(8): return
Anche le informazioni su conteggi e coverage possono essere registrate, prooprio come con l'interfaccia da riga di comando. I dati devono essere esplicitamente salvati, usando una istanza di
CoveragaResults
dall'oggetto
Trace
-
mport 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')
$ python trace_CoverageResults.py ricorsione(2) ricorsione(1) ricorsione(0) $ find coverdir2 coverdir2 coverdir2/trace_example.recurse.cover
Ecco il contenuto di coverdir2/trace_example.recurse.cover :
#!/usr/bin/env python # -*- coding: UTF-8 -*- >>>>>> def recurse(level): 3: print 'ricorsione(%s)' % level 3: if level: 2: recurse(level-1) 3: return >>>>>> def not_called(): >>>>>> print 'Questa funzione non è mai chiamata.'
Per salvare i dati di conteggio per la generazione di rapporti, si usano i parametri di
Trace
infile
ed
outfile
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')
Si passa un nome di file a infile per leggere dati precedentemente salvati, ed un nome di file a outfile per scrivere i nuovi risultati dopo la tracciatura. Se infile ed outfile sono uguali, si ottiene l'effetto di aggiornamento del file con dati comulativi.
$ python trace_report..py ricorsione(2) ricorsione(1) ricorsione(0) lines cov% module (path) 7 57% trace_example.recurse (/home/robby/pydev/pymotw-it/dumpscripts/trace_example/recurse.py)
Il cotruttore di Trace riceve parecchi diversi parametri opzionali per controllare il comportamento in fase di esecuzione.
Vedere anche: