Seguimiento de un programa mientras se ejecuta¶
Hay dos formas de inyectar código para ver la ejecución de un programa: rastreo y perfilado. Son similares, pero están destinados a diferentes propósitos y, por lo tanto, tienen diferentes restricciones. La manera más fácil, pero menos eficiente, de monitorear un programa es a través de un gancho de rastreo, que se puede usar para escribir un depurador, monitorear la cobertura del código o lograr muchos otros propósitos.
El gancho de rastreo se modifica pasando una función de devolución de llamada a
sys.settrace()
. La devolución de llamada recibirá tres argumentos: el
marco de la pila del código que se está ejecutando, una cadena que nombra el
tipo de notificación y un valor de argumento específico del evento.
the table below enumera los siete tipos de eventos
para diferentes niveles de información que ocurren cuando se ejecuta un
programa.
Evento | Cuando ocurre | Valor de argumento |
---|---|---|
call | Antes de ejecutar una función | None |
line | Antes de ejecutar una línea | None |
return | Antes de que regrese una función | El valor que se devuelve |
exception | Después de que se produce una excepción | La tupla (exception, value, traceback) |
c_call | Antes de que una función C sea invocada | El objecto de función C |
c_return | Después de que una función C regrese | None |
c_exception | Después de que una función C arroje un error | None |
Seguimiento de llamadas de función¶
Se genera un evento call
antes de cada llamada de función. El marco pasado
a la devolución de llamada se puede utilizar para averiguar qué función se
llama y desde dónde.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #!/usr/bin/env python3
# encoding: utf-8
import sys
def trace_calls(frame, event, arg):
if event != 'call':
return
co = frame.f_code
func_name = co.co_name
if func_name == 'write':
# Ignore write() calls from printing
return
func_line_no = frame.f_lineno
func_filename = co.co_filename
if not func_filename.endswith('sys_settrace_call.py'):
# Ignore calls not in this module
return
caller = frame.f_back
caller_line_no = caller.f_lineno
caller_filename = caller.f_code.co_filename
print('* Call to', func_name)
print('* on line {} of {}'.format(
func_line_no, func_filename))
print('* from line {} of {}'.format(
caller_line_no, caller_filename))
return
def b():
print('inside b()\n')
def a():
print('inside a()\n')
b()
sys.settrace(trace_calls)
a()
|
Este ejemplo ignora las llamadas a write()
, como la usa print
para
escribir en sys.stdout
.
$ python3 sys_settrace_call.py
* Call to a
* on line 35 of sys_settrace_call.py
* from line 41 of sys_settrace_call.py
inside a()
* Call to b
* on line 31 of sys_settrace_call.py
* from line 37 of sys_settrace_call.py
inside b()
Seguimiento de funciones internas¶
El enlace de rastreo puede devolver un nuevo enlace que se utilizará dentro del nuevo ámbito (la función de rastreo local). Es posible, por ejemplo, controlar el rastreo para ejecutar solo línea por línea dentro de ciertos módulos o funciones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | #!/usr/bin/env python3
# encoding: utf-8
import functools
import sys
def trace_lines(frame, event, arg):
if event != 'line':
return
co = frame.f_code
func_name = co.co_name
line_no = frame.f_lineno
print('* {} line {}'.format(func_name, line_no))
def trace_calls(frame, event, arg, to_be_traced):
if event != 'call':
return
co = frame.f_code
func_name = co.co_name
if func_name == 'write':
# Ignore write() calls from printing
return
line_no = frame.f_lineno
filename = co.co_filename
if not filename.endswith('sys_settrace_line.py'):
# Ignore calls not in this module
return
print('* Call to {} on line {} of {}'.format(
func_name, line_no, filename))
if func_name in to_be_traced:
# Trace into this function
return trace_lines
return
def c(input):
print('input =', input)
print('Leaving c()')
def b(arg):
val = arg * 5
c(val)
print('Leaving b()')
def a():
b(2)
print('Leaving a()')
tracer = functools.partial(trace_calls, to_be_traced=['b'])
sys.settrace(tracer)
a()
|
En este ejemplo, la lista de funciones se mantiene en la variable
:py``to_be_traced``, por lo que cuando se ejecuta trace_calls()
puede
devolver trace_lines()
para habilitar el seguimiento dentro de b()
.
$ python3 sys_settrace_line.py
* Call to a on line 49 of sys_settrace_line.py
* Call to b on line 43 of sys_settrace_line.py
* b line 44
* b line 45
* Call to c on line 38 of sys_settrace_line.py
input = 10
Leaving c()
* b line 46
Leaving b()
Leaving a()
Seguimiento de la pila¶
Otra forma útil de usar los ganchos es mantenerse al día con las funciones que
se están llamando y cuáles son sus valores de retorno. Para monitorear los
valores de retorno, observa el evento return
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #!/usr/bin/env python3
# encoding: utf-8
import sys
def trace_calls_and_returns(frame, event, arg):
co = frame.f_code
func_name = co.co_name
if func_name == 'write':
# Ignore write() calls from printing
return
line_no = frame.f_lineno
filename = co.co_filename
if not filename.endswith('sys_settrace_return.py'):
# Ignore calls not in this module
return
if event == 'call':
print('* Call to {} on line {} of {}'.format(
func_name, line_no, filename))
return trace_calls_and_returns
elif event == 'return':
print('* {} => {}'.format(func_name, arg))
return
def b():
print('inside b()')
return 'response_from_b '
def a():
print('inside a()')
val = b()
return val * 2
sys.settrace(trace_calls_and_returns)
a()
|
La función de rastreo local se usa para ver eventos de retorno, por lo que
trace_calls_and_returns()
necesita devolver una referencia a sí misma
cuando se llama a una función, por lo que el valor de retorno puede ser
monitoreado.
$ python3 sys_settrace_return.py
* Call to a on line 32 of sys_settrace_return.py
inside a()
* Call to b on line 27 of sys_settrace_return.py
inside b()
* b => response_from_b
* a => response_from_b response_from_b
Propagación de excepciones¶
Las excepciones se pueden monitorear buscando el evento exception
en una
función de rastreo local. Cuando se produce una excepción, se llama al enlace
de rastreo con una tupla que contiene el tipo de excepción, el objeto de
excepción y un objeto de rastreo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #!/usr/bin/env python3
# encoding: utf-8
import sys
def trace_exceptions(frame, event, arg):
if event != 'exception':
return
co = frame.f_code
func_name = co.co_name
line_no = frame.f_lineno
exc_type, exc_value, exc_traceback = arg
print(('* Tracing exception:\n'
'* {} "{}"\n'
'* on line {} of {}\n').format(
exc_type.__name__, exc_value, line_no,
func_name))
def trace_calls(frame, event, arg):
if event != 'call':
return
co = frame.f_code
func_name = co.co_name
if func_name in TRACE_INTO:
return trace_exceptions
def c():
raise RuntimeError('generating exception in c()')
def b():
c()
print('Leaving b()')
def a():
b()
print('Leaving a()')
TRACE_INTO = ['a', 'b', 'c']
sys.settrace(trace_calls)
try:
a()
except Exception as e:
print('Exception handler:', e)
|
Ten cuidado de limitar dónde se aplica la función local porque algunas de las partes internas de los mensajes de error de formato generan e ignoran sus propias excepciones. El gancho de rastreo ve cada excepción, ya sea que la persona que llama la capture o la ignore o no.
$ python3 sys_settrace_exception.py
* Tracing exception:
* RuntimeError "generating exception in c()"
* on line 31 of c
* Tracing exception:
* RuntimeError "generating exception in c()"
* on line 35 of b
* Tracing exception:
* RuntimeError "generating exception in c()"
* on line 40 of a
Exception handler: generating exception in c()
Ver también
profile
– La documentación del móduloprofile
muestra cómo usar un perfilador listo para usar.trace
– El módulotrace
implementa varias funciones de análisis de código.- Types and Members – Las descripciones de los objetos de marco, de código y sus atributos.
- Tracing python code
– Otro tutorial de
settrace()
. - Wicked hack: Python bytecode tracing – Los experimentos de Ned Batchelder con el rastreo con más granularidad que el nivel de línea de origen.
- smiley – Rastreado de aplicaciones Python