如何在函数调用时打印它们?
在调试一个Python脚本的时候,我很想知道我整个程序的调用栈。理想的情况是,如果Python有一个命令行选项,可以让它在调用每个函数的时候都打印出函数名(我查过man Python2.7
,但没有找到这样的选项)。
因为这个脚本里有很多函数,我希望能避免在每个函数和类的开头都加上打印语句,如果可以的话。
一个折中的解决办法是使用PyDev的调试器,设置几个断点,然后查看程序在特定点的调用栈,所以我暂时会采用这个方法。
不过,我还是希望能看到一个完整的函数调用列表,如果有这样的办法的话。
9 个回答
我参考了kindall的回答,并在此基础上进行了扩展。我制作了以下模块:
"""traceit.py
Traces the call stack.
Usage:
import sys
import traceit
sys.setprofile(traceit.tracefunc)
"""
import sys
WHITE_LIST = {'trade'} # Look for these words in the file path.
EXCLUSIONS = {'<'} # Ignore <listcomp>, etc. in the function name.
def tracefunc(frame, event, arg):
if event == "call":
tracefunc.stack_level += 1
unique_id = frame.f_code.co_filename+str(frame.f_lineno)
if unique_id in tracefunc.memorized:
return
# Part of filename MUST be in white list.
if any(x in frame.f_code.co_filename for x in WHITE_LIST) \
and \
not any(x in frame.f_code.co_name for x in EXCLUSIONS):
if 'self' in frame.f_locals:
class_name = frame.f_locals['self'].__class__.__name__
func_name = class_name + '.' + frame.f_code.co_name
else:
func_name = frame.f_code.co_name
func_name = '{name:->{indent}s}()'.format(
indent=tracefunc.stack_level*2, name=func_name)
txt = '{: <40} # {}, {}'.format(
func_name, frame.f_code.co_filename, frame.f_lineno)
print(txt)
tracefunc.memorized.add(unique_id)
elif event == "return":
tracefunc.stack_level -= 1
tracefunc.memorized = set()
tracefunc.stack_level = 0
示例用法
import traceit
sys.setprofile(traceit.tracefunc)
示例输出:
API.getFills() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 331
API._get_req_id() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1053
API._wait_till_done() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1026
---API.execDetails() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1187
-------Fill.__init__() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 256
--------Price.__init__() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 237
-deserialize_order_ref() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 644
--------------------Port() # C:\Python37-32\lib\site-packages\helpers\trade\mdb.py, 647
API.commissionReport() # C:\Python37-32\lib\site-packages\helpers\trade\tws3.py, 1118
功能:
- 忽略Python语言内部的函数。
- 可以选择忽略重复的函数调用。
- 使用sys.setprofile()代替sys.settrace(),这样速度更快。
还有一个很不错的工具就是 trace 模块。它有三种方式可以显示函数名称。
下面是一个示例文件 foo.py
:
def foo():
bar()
def bar():
print("in bar!")
foo()
- 使用
-l/--listfuncs
来 列出所有函数:
$ python -m trace --listfuncs foo.py
in bar!
functions called:
filename: /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/trace.py, modulename: trace, funcname: _unsettrace
filename: foo.py, modulename: foo, funcname: <module>
filename: foo.py, modulename: foo, funcname: bar
filename: foo.py, modulename: foo, funcname: foo
- 使用
-t/--trace
来 显示每一行代码的执行情况。
$python -m trace --trace foo.py
--- modulename: foo, funcname: <module>
foo.py(1): def foo():
foo.py(4): def bar():
foo.py(7): foo()
--- modulename: foo, funcname: foo
foo.py(2): bar()
--- modulename: foo, funcname: bar
foo.py(5): print("in bar!")
in bar!
- 使用
-T/--trackcalls
来 列出函数之间的调用关系。
$ python -m trace --trackcalls foo.py
in bar!
calling relationships:
*** /usr/lib/python3.8/trace.py ***
--> foo.py
trace.Trace.runctx -> foo.<module>
*** foo.py ***
foo.<module> -> foo.foo
foo.foo -> foo.bar
你可以用一个追踪函数来实现这个功能(感谢Spacedman对原版的改进,让它可以追踪返回值并使用更好的缩进):
def tracefunc(frame, event, arg, indent=[0]):
if event == "call":
indent[0] += 2
print("-" * indent[0] + "> call function", frame.f_code.co_name)
elif event == "return":
print("<" + "-" * indent[0], "exit function", frame.f_code.co_name)
indent[0] -= 2
return tracefunc
import sys
sys.setprofile(tracefunc)
main() # or whatever kicks off your script
需要注意的是,函数的代码对象通常和它对应的函数名字是一样的,但并不总是如此,因为函数可以动态创建。不幸的是,Python并不会在栈上跟踪函数对象(我有时幻想能为此提交一个补丁)。不过,在大多数情况下,这样的做法已经“足够好了”。
如果这成了一个问题,你可以从源代码中提取“真实”的函数名——Python会跟踪文件名和行号——或者让垃圾回收器找出哪个函数对象引用了这个代码对象。可能会有多个函数共享同一个代码对象,但它们的任何一个名字都可能是“足够好的”。
四年后再回过头来看这个问题,我得提一下,在Python 2.6及以后的版本中,使用 sys.setprofile()
而不是 sys.settrace()
可以获得更好的性能。可以使用相同的追踪函数;只是配置函数只在进入或退出一个函数时被调用,所以函数内部的代码可以以全速执行。