-1

有没有办法以编程方式获取 Python 函数返回的语句的行号?让我们考虑以下示例:

def foo(i: int) -> str:
    if i == 1:
        return 'he'
    elif i == 2:
        return 'ha'
    return 'he'

如果输入 1 或 3,函数将返回'he'。但是,除非我可以单步执行代码,否则我不会知道return 'he'实际执行的是哪个。

我的应用程序允许用户从 GUI 中输入 Python 中的业务规则,并让他们自己测试规则。由于我的应用程序无法单步执行代码(它将执行任务委托给独立的 python 引擎并需要一个字符串或异常。)我想找到一种方法来获取返回的行号,以便我可以突出显示GUI 编辑器上的相应行。

一种肮脏的做法是让函数返回一个行号和字符串的元组,但理想情况下,我想让用户免于这样做的痛苦。

4

2 回答 2

1

sys.settrace()您可以使用;让 Python 通知您任何返回。这是 Python 在某些事件发生时调用的钩子函数,也是典型的调试器和分析器挂钩到 Python 的方式。

每当 Python 进入新的本地范围(用于函数调用、类体、理解和生成器表达式)时,您注册的函数sys.settrace()只会被调用事件调用。然后,您可以返回None(不跟踪此本地范围),或将用于该范围内的lineexceptionreturn事件的跟踪函数。在 Python 3.7 中,您可以在 frame 对象上设置选项,以进一步控制调用 per-scope 跟踪函数的详细程度;您可以禁用每行事件,甚至启用每操作码事件。

你可以用它来记录这样的返回事件;我对调用返回事件都使用一种跟踪方法:

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+ 之上,但不会禁用行跟踪。而且作为一个通用的调试器框架,它为每个跟踪事件增加了更高的开销。这意味着您正在检测的代码的执行速度较慢。

于 2019-02-05T17:39:43.150 回答
1

bdb模块允许您在每个帧返回之前检查它,因此您应该能够f_lineno在该点检索 foo 的最终返回。例子:

from bdb import Bdb
class ReturnWatcher(Bdb):
    def __init__(self):
        self.last_encountered_return_line = None
        super().__init__()
    def user_return(self, frame, return_value):
        self.last_encountered_return_line = frame.f_lineno

def foo(i):
    if(i==1):
        return 'he'
    elif(i==2):
        return 'ha'
    return 'he'

x = ReturnWatcher()
x.runcall(foo, 1)
print("Last return statement executed on line", x.last_encountered_return_line)
x.runcall(foo, 2)
print("Last return statement executed on line", x.last_encountered_return_line)
x.runcall(foo, 3)
print("Last return statement executed on line", x.last_encountered_return_line)

结果:

Last return statement executed on line 11
Last return statement executed on line 13
Last return statement executed on line 14
于 2019-02-05T17:10:39.807 回答