web-dev-qa-db-fra.com

Comment puis-je imprimer les fonctions comme on les appelle

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.

54
James

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.

86
kindall

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)

10
David Wolever

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.

7
Sven Marnach
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

traceback

5
Abhijit

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.

2
jeorgen

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:

  • Ignore les fonctions internes du langage Python.
  • Ignore les appels de fonction répétés (facultatif). 
  • Utilise sys.setprofile () au lieu de sys.settrace () pour plus de rapidité.
1
ChaimG

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.

1
Vincent Fenet

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.

0
Chris Hunt