43

set -x请建议与 shell 脚本等效的 Python 命令。

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

4

4 回答 4

41

您可以使用跟踪模块:

python -m trace -t your_script.py

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

于 2013-04-02T09:20:20.003 回答
22

为了获得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

对比

$ 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.html和http://www.dalkescientific.com/writings/diary/archive/2005/04/20/tracing_python_code.html

于 2015-10-31T08:52:42.743 回答
3

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

还有一点需要注意。根据sys.settrace()文档:

每当输入新的本地范围时,都会调用跟踪函数(事件设置为“调用”)

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

为了保持一切整洁,我决定将所有内容放入一个名为setx.py. 代码应该是不言自明的。然而,为了兼容 Python 3,需要一段代码,它处理 Python 2 和 3 在模块导入方式方面的差异。这是解释here。该代码现在应该也适用于 Python 2 和 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)

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

##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.pyfile2.py

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

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

然后输出如下所示:

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
于 2018-03-15T14:53:32.667 回答
0

还有viztracer,它与 GUI 类似trace,但更完整。

你可以像这样使用它:

$ pip install viztracer --user
$ viztracer --log_func_args --log_func_retval source.py

它生成一个可以像这样可视化的报告vizviewer report.json

于 2022-02-19T01:40:26.040 回答