web-dev-qa-db-fra.com

Python débogage: obtenir le nom de fichier et le numéro de ligne à partir desquels une fonction est appelée?

Je suis en train de construire un système assez complexe en Python, et quand je débogue, je mets souvent des instructions d'impression simples dans plusieurs scripts. Pour garder une vue d'ensemble, je souhaite également souvent imprimer le nom de fichier et le numéro de ligne où se trouve l'instruction d'impression. Je peux bien sûr le faire manuellement, ou avec quelque chose comme ça:

from inspect import currentframe, getframeinfo
print getframeinfo(currentframe()).filename + ':' + str(getframeinfo(currentframe()).lineno) + ' - ', 'what I actually want to print out here'

qui imprime quelque chose comme:

filenameX.py:273 - ce que je veux vraiment imprimer ici

Pour plus de simplicité, je veux pouvoir faire quelque chose comme:

print debuginfo(), 'what I actually want to print out here'

Je l'ai donc mis dans une fonction quelque part et j'ai essayé de faire:

from debugutil import debuginfo
print debuginfo(), 'what I actually want to print out here'
print debuginfo(), 'and something else here'

malheureusement, je reçois:

debugutil.py:3 - what I actually want to print out here
debugutil.py:3 - and something else here

Il affiche le nom de fichier et le numéro de ligne sur lesquels j'ai défini la fonction, au lieu de la ligne sur laquelle j'appelle debuginfo (). Cela est évident, car le code se trouve dans le fichier debugutil.py.

Donc ma question est en fait: Comment puis-je obtenir le nom de fichier et le numéro de ligne à partir desquels cette fonction debuginfo () est appelée? Toutes les astuces sont les bienvenues!

34
kramer65

La fonction inspect.stack() renvoie une liste enregistrements de trames , en commençant par l'appelant et en sortant, que vous pouvez utiliser pour obtenir les informations souhaitées:

from inspect import getframeinfo, stack

def debuginfo(message):
    caller = getframeinfo(stack()[1][0])
    print "%s:%d - %s" % (caller.filename, caller.lineno, message)

def grr(arg):
    debuginfo(arg)

grr("aargh")

Sortie :

example.py:8 - aargh
51
Zero Piraeus

Si vous placez votre code de trace dans une autre fonction et appelez cela à partir de votre code principal, vous devez vous assurer que vous obtenez les informations de pile du grand-parent, pas du parent ou de la fonction de trace elle-même

Voici un exemple de système profond à 3 niveaux pour clarifier davantage ce que je veux dire. Ma fonction principale appelle une fonction de trace, qui appelle encore une autre fonction pour faire le travail.

######################################

import sys, os, inspect, time
time_start = 0.0                    # initial start time

def trace_libary_init():
    global time_start

    time_start = time.time()      # when the program started

def trace_library_do(relative_frame, msg=""):
    global time_start

    time_now = time.time()

        # relative_frame is 0 for current function (this one), 
        # 1 for direct parent, or 2 for grand parent.. 

    total_stack         = inspect.stack()                   # total complete stack
    total_depth         = len(total_stack)                  # length of total stack
    frameinfo           = total_stack[relative_frame][0]    # info on rel frame
    relative_depth      = total_depth - relative_frame      # length of stack there

        # Information on function at the relative frame number

    func_name           = frameinfo.f_code.co_name
    filename            = os.path.basename(frameinfo.f_code.co_filename)
    line_number         = frameinfo.f_lineno                # of the call
    func_firstlineno    = frameinfo.f_code.co_firstlineno

    fileline            = "%s:%d" % (filename, line_number)
    time_diff           = time_now - time_start

    print("%13.6f %-20s %-24s %s" % (time_diff, fileline, func_name, msg))

################################

def trace_do(msg=""):
    trace_library_do(1, "trace within interface function")
    trace_library_do(2, msg)
    # any common tracing stuff you might want to do...

################################

def main(argc, argv):
    rc=0
    trace_libary_init()
    for i in range(3):
        trace_do("this is at step %i" %i)
        time.sleep((i+1) * 0.1)         # in 1/10's of a second
    return rc

rc=main(sys.argv.__len__(), sys.argv)
sys.exit(rc)

Cela imprimera quelque chose comme:

$ python test.py 
    0.000005 test.py:39           trace_do         trace within interface func
    0.001231 test.py:49           main             this is at step 0
    0.101541 test.py:39           trace_do         trace within interface func
    0.101900 test.py:49           main             this is at step 1
    0.302469 test.py:39           trace_do         trace within interface func
    0.302828 test.py:49           main             this is at step 2

La fonction trace_library_do () en haut est un exemple de quelque chose que vous pouvez déposer dans une bibliothèque, puis l'appeler à partir d'autres fonctions de traçage. La valeur de profondeur relative contrôle quelle entrée dans la pile python est imprimée.

J'ai montré extraire quelques autres valeurs intéressantes dans cette fonction, comme le numéro de ligne de début de la fonction, la profondeur totale de la pile et le chemin d'accès complet au fichier. Je ne l'ai pas montré, mais les variables globales et locales de la fonction sont également disponibles dans inspect, ainsi que la trace complète de la pile vers toutes les autres fonctions sous la vôtre. Il y a plus qu'assez d'informations avec ce que je montre ci-dessus pour faire des traces de synchronisation d'appel/retour hiérarchiques. En fait, cela ne va pas beaucoup plus loin que de créer les principales parties de votre propre débogueur de niveau source à partir d'ici - et tout cela est principalement assis là en attendant d'être utilisé.

Je suis sûr que quelqu'un objectera que j'utilise des champs internes avec des données renvoyées par les structures d'inspection, car il peut bien y avoir des fonctions d'accès qui font la même chose pour vous. Mais je les ai trouvés en parcourant ce type de code dans un débogueur python, et ils fonctionnent au moins ici. J'exécute python 2.7.12 , vos résultats peuvent être très importants si vous utilisez une version différente.

Dans tous les cas, je vous recommande fortement d'importer le code d'inspection dans votre propre code python, et regardez ce qu'il peut vous fournir - Surtout si vous pouvez parcourir votre code en une seule étape un bon python débogueur. Vous en apprendrez beaucoup sur le fonctionnement de python, et découvrirez à la fois les avantages du langage et ce qui se passe derrière le rideau pour rendre cela possible.

Le suivi complet du niveau source avec des horodatages est un excellent moyen d'améliorer votre compréhension de ce que fait votre code, en particulier dans un environnement dynamique en temps réel. La grande chose à propos de ce type de code de trace est qu'une fois qu'il est écrit, vous n'avez pas besoin du support du débogueur pour le voir.

3
pdb

Mettez simplement le code que vous avez publié dans une fonction:

from inspect import currentframe, getframeinfo

def my_custom_debuginfo(message):
    print getframeinfo(currentframe()).filename + ':' + str(getframeinfo(currentframe()).lineno) + ' - ', message

puis utilisez-le comme vous le souhaitez:

# ... some code here ...
my_custom_debuginfo('what I actually want to print out here')
# ... more code ...

Je vous recommande de mettre cette fonction dans un module séparé, de cette façon, vous pouvez la réutiliser chaque fois que vous en avez besoin.

0
Raydel Miranda

J'ai découvert cette question pour un problème quelque peu connexe, mais je voulais plus de détails sur l'exécution (et je ne voulais pas installer un package de graphique d'appel complet).

Si vous voulez des informations plus détaillées, vous pouvez récupérer un traceback complet avec le module de bibliothèque standard traceback , et soit cacher l'objet pile (une liste de tuples) avec traceback.extract_stack() ou l'imprimer out avec traceback.print_stack(). C'était plus adapté à mes besoins, j'espère que cela aide quelqu'un d'autre!

0
CrepeGoat