【问题标题】:Getting line number of return statement获取return语句的行号
【发布时间】:2019-02-05 16:51:48
【问题描述】:

有没有办法以编程方式获取 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 编辑器上的相应行。

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

【问题讨论】:

  • @zvone:简短的回答实际上是“是”。您的长问题的长答案是:因为这是理想情况下 Python 代码的 IDE GUI 应该能够做到的,所以向用户展示他们的代码正在做什么以帮助开发

标签: python python-3.x return


【解决方案1】:

bdb 模块可让您在每个帧返回之前检查它,因此您应该能够在此时检索 foo 最终返回的f_lineno。示例:

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

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

    • @user2136168:很高兴能帮上忙!如果您觉得它对您有用,请随时 accept one of the answers here。 :-) 选择你认为对你最有帮助的,或者没有,选择权在你。
    猜你喜欢
    • 2020-04-11
    • 1970-01-01
    • 2017-11-04
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 2017-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多