shell中`set -x`的Python等价物是什么?

时间:2013-04-02 09:17:59

标签: python shell debugging

请建议Python命令,它等同于shell脚本中的set -x

有没有办法打印/记录Python执行的每个源文件行?

3 个答案:

答案 0 :(得分:28)

您可以使用trace模块:

python -m trace -t your_script.py

上面的命令行将显示执行时的每一行代码。

答案 1 :(得分:16)

要使用bash -x模块获得trace的正确等效值,需要使用--ignore-dir来阻止导入的每个模块的源行的打印,例如python -m trace --trace --ignore-dir /usr/lib/python2.7 --ignore-dir /usr/lib/pymodules repost.py,根据需要为其他模块位置添加更多--ignore-dir指令。

这在尝试定位慢速加载模块(例如requests时变得很重要,这些模块在慢速机器上吐出数百万条源线几分钟。正确使用--ignore-dir可将时间缩短至几秒钟,并仅显示您自己代码中的行。

$ 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

这并没有真正回答你的问题;你要求Python等价于set -x。一种简单的近似方法是使用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()

,在运行时,给出:

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

从跟踪输出中删除“TRACING.pop()”行是留给读者的练习。

来源:https://pymotw.com/2/sys/tracing.htmlhttp://www.dalkescientific.com/writings/diary/archive/2005/04/20/tracing_python_code.html

答案 2 :(得分:0)

我非常喜欢@ jcomeau_ictx的answer,但它有一个小缺陷,这就是为什么我对它进行了一些扩展。问题是,如果要跟踪的所有代码都在使用python file.py调用的文件中(让我们称之为主机文件),jcomeau_ictx的'traceit'函数才能正常工作。如果您调用任何导入的函数,则会获得大量没有代码的行号。原因是line = linecache.getline(sys.argv[0], lineno)总是试图从主机文件(sys.argv[0])获取代码行。这很容易纠正,因为可以在frame.f_code.co_filename中找到实际包含跟踪代码行的文件的名称。现在这可能产生大量输出,这就是为什么人们可能希望有更多的控制权。

还有一点需要注意。根据{{​​1}}文档:

  

每当a调用跟踪函数(事件设置为'call')   输入新的本地范围

换句话说,要跟踪的代码必须在函数内部。

为了保持一切整洁,我决定将所有内容放入一个名为sys.settrace()的文件中。代码应该是不言自明的。但是,Python 3兼容性需要一段代码,它处理Python 2和3之间在模块导入方式方面的差异。这是here的解释。现在代码也适用于Python 2和3。

setx.py

然后我用这段代码测试功能:

##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)

文件##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() file1.py如下所示:

file2.py

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

然后输出如下:

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