Quel est L'équivalent en Python de 'set-x' dans shell?

s'il vous Plaît suggérer de commande Python, qui est l'équivalent de set -x dans les scripts shell.

Existe-t-il un moyen d'imprimer/enregistrer chaque ligne de fichier source exécutée par Python?

28
demandé sur Dave Everitt 2013-04-02 13:17:58

3 réponses

Vous pouvez utiliser le trace module:

python -m trace -t your_script.py

La ligne de commande ci-dessus va afficher chaque ligne de code est exécuté.

26
répondu Frédéric Hamidi 2013-04-02 09:31:18

pour obtenir un bon équivalent de bash -xtrace module, il faut utiliser --ignore-dir pour bloquer l'impression des lignes source de chaque module importé, par exemple python -m trace --trace --ignore-dir /usr/lib/python2.7 --ignore-dir /usr/lib/pymodules repost.py, en ajoutant plus de --ignore-dir directives nécessaires pour les autres emplacements de module.

ceci devient important lorsque vous tentez de localiser des modules de chargement lent tels que requests qui crachent des millions de lignes de source pendant plusieurs minutes sur une machine lente. la bonne utilisation de --ignore-dir réduit le temps à quelques minutes secondes, et affiche seulement les lignes de votre propre code.

$ time python -m trace --trace repost.py 2>&1 | wc
3710176 16165000 200743489

real    1m54.302s
user    2m14.360s
sys 0m1.344s

vs.

$ time python -m trace --trace --ignore-dir /usr/lib/python2.7 --ignore-dir /usr/lib/pymodules repost.py 2>&1 | wc
     42     210    2169

real    0m12.570s
user    0m12.421s
sys 0m0.108s

cela ne répond pas vraiment à votre question; vous avez demandé un équivalent Python de set -x. une façon simple de rapprocher c'est avec sys.settrace():

jcomeau@aspire:/tmp$ cat test.py
#!/usr/bin/python -OO
'''
test program for sys.settrace
'''
import sys, linecache
TRACING = []

def traceit(frame, event, arg):
    if event == "line":
        lineno = frame.f_lineno
        line = linecache.getline(sys.argv[0], lineno)
        if TRACING:
            print "%d: %s" % (lineno, line.rstrip())
    return traceit

def test():
    print 'this first line should not trace'
    TRACING.append(True)
    print 'this line and the following ought to show'
    print "that's all folks"
    TRACING.pop()
    print 'this last line should not trace'

if __name__ == '__main__':
    sys.settrace(traceit)
    test()

qui, lorsqu'il est exécuté, donne:

jcomeau@aspire:/tmp$ ./test.py
this first line should not trace
19:     print 'this line and the following ought to show'
this line and the following ought to show
20:     print "that's all folks"
that's all folks
21:     TRACING.pop()
this last line should not trace

élimination du traçage de la ligne.pop () ' de la sortie trace est laissé comme un exercice pour le lecteur.

sources: https://pymotw.com/2/sys/tracing.html et http://www.dalkescientific.com/writings/diary/archive/2005/04/20/tracing_python_code.html

13
répondu jcomeau_ictx 2015-10-31 08:52:42

j'ai bien aimé @jcomeau_ictx réponse très bien, mais il a un petit défaut, c'est pourquoi j'ai prolongé un peu. Le problème est que la fonction 'traceit' de jcomeau_ictx ne fonctionne correctement que si tout le code à tracer est dans le fichier qui est appelé avec python file.py (appelons-le fichier host). Si vous appelez les fonctions importées, vous obtenez beaucoup de numéros de ligne sans code. La raison pour cela est que line = linecache.getline(sys.argv[0], lineno) essaie toujours d'obtenir la ligne de code dans le fichier host (sys.argv[0]). Ceci peut facilement être corrigé, car le nom du fichier qui contient réellement la ligne tracée du code peut être trouvé dans frame.f_code.co_filename. Cela peut maintenant produire beaucoup de sortie, c'est pourquoi on voudrait probablement avoir un peu plus de contrôle.

il y a aussi un autre point à noter. Selon l' sys.settrace() documentation:

la fonction trace est invoquée (avec l'événement défini à 'call') chaque fois qu'un de nouvelles locale est entré

Dans les autres les mots, le code de retracer les a à l'intérieur d'une fonction.

Pour garder tout en ordre, j'ai décidé de tout mettre dans un fichier appelé setx.py. Le code devrait être assez explicite. Il y a, cependant, un morceau de code qui est nécessaire pour la compatibilité Python 3, qui traite des différences entre Python 2 et 3 en ce qui concerne la façon dont les modules sont importés. C'est expliqué ici. Le code devrait maintenant fonctionner aussi bien avec Python 2 qu'avec 3.

##setx.py
from __future__ import print_function
import sys, linecache

##adapted from https://stackoverflow.com/a/33449763/2454357
##emulates bash's set -x and set +x

##for turning tracing on and off
TRACING = False

##FILENAMES defines which files should be traced
##by default this will on only be the host file 
FILENAMES = [sys.argv[0]]

##flag to ignore FILENAMES and alwas trace all files
##off by default
FOLLOWALL = False

def traceit(frame, event, arg):
    if event == "line":
        ##from https://stackoverflow.com/a/40945851/2454357
        while frame.f_code.co_filename.startswith('<frozen'):
            frame = frame.f_back
        filename = frame.f_code.co_filename
##        print(filename, FILENAMES)
        if TRACING and (
            filename in FILENAMES or
            filename+'c' in FILENAMES or
            FOLLOWALL
        ):
            lineno = frame.f_lineno
            line = linecache.getline(filename, lineno)
            print("{}, {}: {}".format(filename, lineno, line.rstrip()))
    return traceit

sys.settrace(traceit)

je teste alors la fonctionnalité avec ce code:

##setx_tester.py
from __future__ import print_function
import os
import setx
from collections import OrderedDict

import file1
from file1 import func1
import file2
from file2 import func2

def inner_func():
    return 15

def test_func():

    x=5
    print('the value of x is', x)

    ##testing function calling:
    print('-'*50)
    ##no further settings
    print(inner_func())
    print(func1())
    print(func2())

    print('-'*50)
    ##adding the file1.py to the filenames to be traced
    ##it appears that the full path to the file is needed:
    setx.FILENAMES.append(file1.__file__)
    print(inner_func())
    print(func1())
    print(func2())

    print('-'*50)
    ##restoring original:
    setx.FILENAMES.pop()

    ##setting that all files should be traced:
    setx.FOLLOWALL = True
    print(inner_func())
    print(func1())
    print(func2())

##turn tracing on:
setx.TRACING = True
outer_test = 42  ##<-- this line will not show up in the output
test_func()

Les fichiers file1.py et file2.py ressembler à ceci:

##file1.py
def func1():
    return 7**2

et

##file2.py
def func2():
    return 'abc'*3

La sortie ressemble alors à ceci:

setx_tester.py, 16:     x=5
setx_tester.py, 17:     print('the value of x is', x)
the value of x is 5
setx_tester.py, 20:     print('-'*50)
--------------------------------------------------
setx_tester.py, 22:     print(inner_func())
setx_tester.py, 12:     return 15
15
setx_tester.py, 23:     print(func1())
49
setx_tester.py, 24:     print(func2())
abcabcabc
setx_tester.py, 26:     print('-'*50)
--------------------------------------------------
setx_tester.py, 29:     setx.FILENAMES.append(file1.__file__)
setx_tester.py, 30:     print(inner_func())
setx_tester.py, 12:     return 15
15
setx_tester.py, 31:     print(func1())
**path to file**/file1.py, 2:     return 7**2
49
setx_tester.py, 32:     print(func2())
abcabcabc
setx_tester.py, 34:     print('-'*50)
--------------------------------------------------
setx_tester.py, 36:     setx.FILENAMES.pop()
setx_tester.py, 39:     setx.FOLLOWALL = True
setx_tester.py, 40:     print(inner_func())
setx_tester.py, 12:     return 15
15
setx_tester.py, 41:     print(func1())
**path to file**/file1.py, 2:     return 7**2
49
setx_tester.py, 42:     print(func2())
**path to file**/file2.py, 2:     return 'abc'*3
abcabcabc
0
répondu Thomas Kühn 2018-03-15 14:53:32