105 votes

Comment puis-je imprimer les fonctions telles qu'elles sont appelées?

Lors du débogage d'un script Python, j'aimerais vraiment connaître la pile d'appels pour l'ensemble de mon programme. Une situation idéale serait s'il existait un drapeau en ligne de commande pour Python qui amènerait Python à imprimer tous les noms de fonction au fur et à mesure de leur appel (j'ai consulté 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 d'impression au début de chaque fonction et/ou classe, si possible.

Une solution intermédiaire consisterait à utiliser le débogueur de PyDev, placer quelques points d'arrêt et vérifier la pile d'appels à des points donnés dans mon programme, donc j'utiliserai cette approche pour le moment.

J'aimerais toujours voir une liste complète de toutes les fonctions appelées tout au long de la vie du programme, si une méthode de ce genre existe.

141voto

kindall Points 60645

Vous pouvez faire cela avec une fonction de trace (props à Spacedman pour avoir amélioré la version originale de cette fonction pour suivre les retours et utiliser un bel alignement) :

def tracefunc(frame, event, arg, indent=[0]):
      if event == "call":
          indent[0] += 2
          print("-" * indent[0] + "> appel de la fonction", frame.f_code.co_name)
      elif event == "return":
          print("<" + "-" * indent[0], "sortie de la fonction", frame.f_code.co_name)
          indent[0] -= 2
      return tracefunc

import sys
sys.setprofile(tracefunc)

main()   # ou tout ce qui lance votre script

Remarquez qu'un objet de code de fonction a généralement le même nom que la fonction associée, mais pas toujours, car les fonctions peuvent être créées dynamiquement. Malheureusement, Python ne suit pas les objets de fonction sur la pile (j'ai parfois fantasmé sur le fait de soumettre un correctif pour cela). Néanmoins, c'est certainement "suffisant" dans la plupart des cas.

Si cela pose problème, vous pourriez extraire le nom "réel" de la fonction à partir du code source - Python suit le nom du fichier et le numéro de ligne - ou demander au récupérateur de déchets de trouver quel objet de fonction fait référence à l'objet de code. Il pourrait y avoir plusieurs fonctions partageant le même objet de code, mais n'importe lequel de leurs noms pourrait suffire.

En revenant sur ce sujet quatre ans plus tard, il m'est bon de mentionner qu'en Python 2.6 et 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 juste que la fonction de profil n'est appelée que lorsqu'une fonction est entrée ou sortie, donc ce qui est à l'intérieur de la fonction s'exécute à pleine vitesse.

26voto

David Wolever Points 34304

Un autre bon outil à connaître est le module trace. Il existe 3 options d'affichage des noms de fonctions.

Exemple foo.py:

def foo():
   bar()

def bar():
   print("in bar!")

foo()
  1. Utiliser -l/--listfuncs pour lister les fonctions:

    $ python -m trace --listfuncs foo.py in bar!

    Les fonctions appelées : nom du fichier : /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/trace.py, nom du module : trace, nom de la fonction : _unsettrace nom du fichier : foo.py, nom du module : foo, nom de la fonction : nom du fichier : foo.py, nom du module : foo, nom de la fonction : bar nom du fichier : foo.py, nom du module : foo, nom de la fonction : foo

  2. Utiliser -t/--trace pour lister les lignes telles qu'elles sont exécutées.

    $python -m trace --trace foo.py --- nom du module : foo, nom de la fonction : foo.py(1): def foo(): foo.py(4): def bar(): foo.py(7): foo() --- nom du module : foo, nom de la fonction : foo foo.py(2): bar() --- nom du module : foo, nom de la fonction : bar foo.py(5): print("in bar!") in bar!

  3. Utiliser -T/--trackcalls pour lister les appels entre les fonctions

    $ python -m trace --trackcalls foo.py in bar!

    Relations d'appels :

    /usr/lib/python3.8/trace.py --> foo.py trace.Trace.runctx -> foo.

    foo.py foo. -> foo.foo foo.foo -> foo.bar

16voto

ChaimG Points 2634

J'ai pris la réponse de Kindall et j'ai développé dessus. J'ai créé le module suivant:

"""traceit.py

Trace la pile d'appels.

Utilisation:

import sys
import traceit

sys.setprofile(traceit.tracefunc)
"""

import sys

WHITE_LIST = {'trade'}      # Recherche ces mots dans le chemin du fichier.
EXCLUSIONS = {'<'}          # Ignorer , etc. dans le nom de la fonction.

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

        # Une partie du nom du fichier DOIT être dans la liste blanche.
        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

Exemple d'utilisation

import traceit

sys.setprofile(traceit.tracefunc)

Sortie d'exemple:

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:

  • Ignore les fonctions internes du langage Python.
  • Ignore les appels de fonction répétés (optionnel).
  • Utilise sys.setprofile() au lieu de sys.settrace() pour la vitesse.

10voto

Sven Marnach Points 133943

Il y a quelques options. Si un débogueur ne suffit pas, vous pouvez définir une fonction de traçage en utilisant sys.settrace(). Cette fonction sera essentiellement appelée sur chaque ligne de code Python exécutée, mais il est facile d'identifier les appels de fonction -- voir la documentation liée.

Vous pourriez également être intéressé par le module trace, bien qu'il ne fasse pas exactement ce que vous avez demandé. Assurez-vous de jeter un coup d'œil à l'option --trackcalls.

7voto

Abhijit Points 24122
import traceback
def foo():
    traceback.print_stack()
def bar():
    foo()
def car():
    bar():

car()
File "", line 1, in 
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 "", line 1, in 
File "", line 2, in car
File "", line 2, in bar
File "", line 2, in foo

traceback

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X