Lors du débogage d'un script Python, j'aimerais vraiment connaître la pile d'appels de l'ensemble de mon programme. Une situation idéale serait s'il y avait un indicateur de ligne de commande pour python qui obligerait Python à imprimer tous les noms de fonctions tels qu'ils sont appelés (j'ai vérifié man Python2.7
, mais je n'ai rien trouvé de ce genre).
En raison du nombre de fonctions dans ce script, je préférerais ne pas ajouter une instruction print au début de chaque fonction et/ou classe, si possible.
Une solution intermédiaire serait d'utiliser le débogueur de PyDev, de placer quelques points d'arrêt et de vérifier la pile d'appels pour des points donnés dans mon programme. Je vais donc utiliser cette approche pour le moment.
Je préférerais quand même voir une liste complète de toutes les fonctions appelées tout au long du programme, si une telle méthode existe.
Vous pouvez le faire avec une fonction de trace (aide de Spacedman pour améliorer la version originale de this afin de suivre les retours et d’utiliser un retrait gentil de Nice):
def tracefunc(frame, event, arg, indent=[0]):
if event == "call":
indent[0] += 2
print "-" * indent[0] + "> call function", frame.f_code.co_name
Elif event == "return":
print "<" + "-" * indent[0], "exit function", frame.f_code.co_name
indent[0] -= 2
return tracefunc
import sys
sys.settrace(tracefunc)
main() # or whatever kicks off your script
Notez que l'objet de code d'une fonction a généralement le même nom que la fonction associée, mais pas toujours, car les fonctions peuvent être créées de manière dynamique. Malheureusement, Python ne suit pas les objets de fonction sur la pile (j'ai parfois fantasmé de soumettre un patch pour cela). Néanmoins, c’est certainement «suffisant» dans la plupart des cas.
Si cela pose un problème, vous pouvez extraire le nom de la fonction "réelle" du code source (Python effectue le suivi du nom du fichier et du numéro de ligne) ou demander au ramasse-miettes de déterminer quel objet fonction fait référence à l'objet code. Il pourrait y avoir plus d’une fonction partageant l’objet code, mais leur nom pourrait être suffisant.
Pour en revenir à cela quatre ans plus tard, il me revient de mentionner que dans Python 2.6 et les versions ultérieures, vous pouvez obtenir de meilleures performances en utilisant sys.setprofile()
plutôt que sys.settrace()
. La même fonction de trace peut être utilisée; c'est simplement que la fonction de profil n'est appelée que lorsqu'une fonction est entrée ou quittée, son contenu s'exécute donc à pleine vitesse.
Un autre bon outil à prendre en compte est le module trace :
$ cat foo.py
def foo():
bar()
def bar():
print "in bar!"
foo()
$ python -m trace --listfuncs foo.py
in bar!
functions called:
filename: /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/trace.py, modulename: trace, funcname: _unsettrace
filename: foo.py, modulename: foo, funcname:
filename: foo.py, modulename: foo, funcname: bar
filename: foo.py, modulename: foo, funcname: foo
$python -m trace --trace foo.py
--- modulename: foo, funcname:
foo.py(1): def foo():
foo.py(4): def bar():
foo.py(7): foo()
--- modulename: foo, funcname: foo
foo.py(2): bar()
--- modulename: foo, funcname: bar
foo.py(5): print "in bar!"
in bar!
--- modulename: trace, funcname: _unsettrace
trace.py(80): sys.settrace(None)
Il y a quelques options. Si un débogueur ne suffit pas, vous pouvez définir une fonction trace à l’aide de sys.settrace()
. Cette fonction sera essentiellement appelée sur chaque ligne de code Python exécutée, mais il sera facile d'identifier les appels de fonction - voir la documentation liée.
Vous pourriez aussi être intéressé par le module trace
, bien qu'il ne fasse pas exactement ce que vous avez demandé. Assurez-vous de regarder dans l'option --trackcalls
.
import traceback
def foo():
traceback.print_stack()
def bar():
foo()
def car():
bar():
car()
File "<string>", line 1, in <module>
File "C:\Python27\lib\idlelib\run.py", line 97, in main
ret = method(*args, **kwargs)
File "C:\Python27\lib\idlelib\run.py", line 298, in runcode
exec code in self.locals
File "<pyshell#494>", line 1, in <module>
File "<pyshell#493>", line 2, in car
File "<pyshell#490>", line 2, in bar
File "<pyshell#486>", line 2, in foo
Vous pouvez utiliser settrace, comme indiqué ici: Traçage du code python . Utilisez la version vers la fin de la page. Je colle le code de cette page dans mon code pour voir exactement quelles lignes sont exécutées lorsque mon code est exécuté. Vous pouvez également filtrer pour ne voir que les noms des fonctions appelées.
J'ai pris la réponse de kindall et construit dessus.
import sys
WHITE_LIST = ['trade'] # Look for these words in the file path.
EXCLUSIONS = ['<'] # Ignore <listcomp>, etc. in the function name.
def tracefunc(frame, event, arg):
if event == "call":
tracefunc.stack_level += 1
unique_id = frame.f_code.co_filename+str(frame.f_lineno)
if unique_id in tracefunc.memorized:
return
# Part of filename MUST be in white list.
if any(x in frame.f_code.co_filename for x in WHITE_LIST) \
and \
not any(x in frame.f_code.co_name for x in EXCLUSIONS):
if 'self' in frame.f_locals:
class_name = frame.f_locals['self'].__class__.__name__
func_name = class_name + '.' + frame.f_code.co_name
else:
func_name = frame.f_code.co_name
func_name = '{name:->{indent}s}()'.format(
indent=tracefunc.stack_level*2, name=func_name)
txt = '{: <40} # {}, {}'.format(
func_name, frame.f_code.co_filename, frame.f_lineno)
print(txt)
tracefunc.memorized.add(unique_id)
Elif event == "return":
tracefunc.stack_level -= 1
tracefunc.memorized = set()
tracefunc.stack_level = 0
sys.setprofile(traceit.tracefunc)
Exemple de sortie:
API.getFills() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 331
API._get_req_id() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1053
API._wait_till_done() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1026
---API.execDetails() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1187
-------Fill.__init__() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 256
--------Price.__init__() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 237
-deserialize_order_ref() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 644
--------------------Port() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 647
API.commissionReport() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1118
Caractéristiques:
Vous pouvez également utiliser un décorateur pour des fonctions spécifiques que vous souhaitez tracer (avec leurs arguments):
import sys
from functools import wraps
class TraceCalls(object):
""" Use as a decorator on functions that should be traced. Several
functions can be decorated - they will all be indented according
to their call depth.
"""
def __init__(self, stream=sys.stdout, indent_step=2, show_ret=False):
self.stream = stream
self.indent_step = indent_step
self.show_ret = show_ret
# This is a class attribute since we want to share the indentation
# level between different traced functions, in case they call
# each other.
TraceCalls.cur_indent = 0
def __call__(self, fn):
@wraps(fn)
def wrapper(*args, **kwargs):
indent = ' ' * TraceCalls.cur_indent
argstr = ', '.join(
[repr(a) for a in args] +
["%s=%s" % (a, repr(b)) for a, b in kwargs.items()])
self.stream.write('%s%s(%s)\n' % (indent, fn.__name__, argstr))
TraceCalls.cur_indent += self.indent_step
ret = fn(*args, **kwargs)
TraceCalls.cur_indent -= self.indent_step
if self.show_ret:
self.stream.write('%s--> %s\n' % (indent, ret))
return ret
return wrapper
Importez simplement ce fichier et ajoutez un @TraceCalls () avant la fonction/méthode que vous souhaitez suivre.
Le hunter
tool fait exactement cela, et plus encore. Par exemple, étant donné:
test.py:
def foo(x):
print(f'foo({x})')
def bar(x):
foo(x)
bar()
La sortie ressemble à:
$ PYTHONHUNTER='module="__main__"' python test.py
test.py:1 call => <module>()
test.py:1 line def foo(x):
test.py:4 line def bar(x):
test.py:7 line bar('abc')
test.py:4 call => bar(x='abc')
test.py:5 line foo(x)
test.py:1 call => foo(x='abc')
test.py:2 line print(f'foo({x})')
foo(abc)
test.py:2 return <= foo: None
test.py:5 return <= bar: None
test.py:7 return <= <module>: None
Il fournit également une syntaxe de requête assez flexible qui permet de spécifier le module, le fichier/lineno, la fonction, etc.