请建议Python命令,它等同于shell脚本中的set -x
。
有没有办法打印/记录Python执行的每个源文件行?
答案 0 :(得分:28)
答案 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.html和http://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