set -x
请建议与 shell 脚本等效的 Python 命令。
有没有办法打印/记录 Python 执行的每个源文件行?
为了获得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
我非常喜欢@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.py
:file2.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
还有viztracer,它与 GUI 类似trace
,但更完整。
你可以像这样使用它:
$ pip install viztracer --user
$ viztracer --log_func_args --log_func_retval source.py
它生成一个可以像这样可视化的报告vizviewer report.json