【发布时间】:2013-03-23 12:39:11
【问题描述】:
请在 shell 脚本中建议相当于 set -x 的 Python 命令。
有没有办法打印/记录 Python 执行的每个源文件行?
【问题讨论】:
-
我正在搜索“等同于
bash -x”。
请在 shell 脚本中建议相当于 set -x 的 Python 命令。
有没有办法打印/记录 Python 执行的每个源文件行?
【问题讨论】:
bash -x”。
【讨论】:
python -m trace -t your_script.py | grep your_script.py 之类的事情,或者它调用的任何你关心执行跟踪的文件。
要使用trace 模块获得bash -x 的适当等效项,需要使用--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 的answer,但它有一个小缺陷,这就是我扩展它的原因。问题是 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
【讨论】: