您可以使用sys.settrace() 让 Python 通知您任何退货;这是 Python 在某些事件发生时调用的挂钩函数,也是典型的调试器和分析器挂钩到 Python 的方式。
您在sys.settrace() 注册的函数只会在call 事件中被调用,只要 Python 进入一个新的本地范围(对于函数调用、类体、理解和生成器表达式)。然后,您可以返回 None(不跟踪此本地范围),或将用于 line、exception 或 return 该范围内的事件。在 Python 3.7 中,您可以在 frame 对象上设置选项,以进一步控制调用 per-scope 跟踪函数的详细程度;您可以禁用每行事件,甚至启用每操作码事件。
你可以用它来记录return这样的事件;我对 call 和 return 事件都使用一种跟踪方法:
import inspect
import sys
class ReturnLines:
def __init__(self):
self.returns = []
self._old_trace = None
def start(self):
self._old_trace = sys.gettrace()
sys.settrace(self.trace)
def stop(self):
sys.settrace(self._old_trace)
def __enter__(self):
self.start()
return self.returns
def __exit__(self, *exc):
self.stop()
def trace(self, frame, event, arg):
filename = None
if frame is not None:
filename = inspect.getsourcefile(frame)
if event == 'call':
if filename == __file__:
# skip ourselves
return
try:
# Python 3.7+: only trace exceptions and returns for this call
frame.f_trace_lines = False
except AttributeError:
pass
return self.trace
elif event == 'return':
self.returns.append((filename, frame.f_lineno, arg))
将其放入一个单独的模块中,并像上下文管理器一样使用该对象:
from return_recorder import ReturnLines
with ReturnLines() as return_lines:
# run the code you want to trace
# ...
上下文管理器允许您访问它添加返回的列表对象(作为(filename, linenumber, returned_object) 元组),因此您可以在上下文管理器中执行代码时访问返回信息:
>>> from return_recorder import ReturnLines
>>> def foo(i: int) -> str:
... if i == 1:
... return 'he'
... elif i == 2:
... return 'ha'
... return 'he'
...
>>> with ReturnLines() as return_lines:
... for i in range(3):
... foo(i)
... print(f'<-- i={i}, returned at line {return_lines[-1][1]}')
...
'he'
<-- i=0, returned at line 6
'he'
<-- i=1, returned at line 3
'ha'
<-- i=2, returned at line 5
>>> for filename, lineno, returned in return_lines:
... print(f'{filename}:{lineno}:{returned!r}')
...
None:6:'he'
None:3:'he'
None:5:'ha'
对于交互式解释器,文件名为None。
bdb 模块是 Kevin 答案的基础,构建在 sys.set_trace() 之上,但不会在 Python 3.7+ 上禁用行跟踪。而且作为一个通用的调试器框架,它为每个跟踪事件增加了更高的开销。这意味着您正在检测的代码的执行速度较慢。