3

考虑这段代码:

#!/usr/bin/env python3

from cmd import Cmd
import readline

class mycmd(Cmd):
    def match_display_hook(self, substitution, matches, longest_match_length):
        someNonexistentMethod()
        print()
        for match in matches:
            print(match)
        print(self.prompt, readline.get_line_buffer(), sep='', end='', flush=True)

    def do_crash(self, s):
        someNonexistentMethod()

    def do_quit(self, s):
        return True

if __name__ == '__main__':
    obj = mycmd()
    readline.set_completion_display_matches_hook(obj.match_display_hook)
    obj.cmdloop()

我希望看到NameError: name 'someNonexistentMethod' is not defined当我运行它并点击TabTab. 但是,实际上似乎根本没有发生任何事情(确实发生了错误,因此打印完成的其他函数不会运行;我只是没有看到错误)。我在运行时确实看到了预期的错误crash,所以我知道错误处理在整个程序中运行良好,但只是在set_completion_display_matches_hook回调内部被破坏了。为什么会这样,我可以做些什么吗?

4

2 回答 2

2

为什么?

我猜这是设计使然。根据rlcompleter 文档

在表达式评估期间引发的任何异常都会被捕获、静音并返回 None。

有关基本原理,请参见rlcompleter 源代码

  • 由完成函数引发的异常会被忽略(通常会导致完成失败)。这是一个特性——因为 readline 将 tty 设备设置为原始(或 cbreak)模式,如果没有一些复杂的 hoopla 来保存、重置和恢复 tty 状态,打印回溯将无法正常工作。

解决方法

作为一种解决方法,为了调试,将您的钩子包装在一个捕获所有异常的函数中(或编写一个函数装饰器),并使用日志记录模块将您的堆栈跟踪记录到文件中:

import logging
logging.basicConfig(filename="example.log", format='%(asctime)s %(message)s')

def broken_function():
    raise NameError("Hi, my name is Name Error")

def logging_wrapper(*args, **kwargs):
    result = None
    try:
        result = broken_function(*args, **kwargs)
    except Exception as ex:
        logging.exception(ex)
    return result

logging_wrapper()

此脚本成功运行,example.log包含日志消息和堆栈跟踪:

2020-11-17 13:55:51,714 Hi, my name is Name Error
Traceback (most recent call last):
  File "/Users/traal/python/./stacktrace.py", line 12, in logging_wrapper
    result = function_to_run()
  File "/Users/traal/python/./stacktrace.py", line 7, in broken_function
    raise NameError("Hi, my name is Name Error")
NameError: Hi, my name is Name Error
于 2020-11-17T12:39:12.657 回答
2

TL;博士

看起来好像readlineC-Binding 只是在调用钩子时忽略异常,当TabTab按下时。


我相信问题的根源可能是 C 绑定readline.c中的这些行(1033-1049)

    r = PyObject_CallFunction(readlinestate_global->completion_display_matches_hook,
                              "NNi", sub, m, max_length);

    m=NULL;

    if (r == NULL ||
        (r != Py_None && PyLong_AsLong(r) == -1 && PyErr_Occurred())) {
        goto error;
    }
    Py_CLEAR(r);

    if (0) {
    error:
        PyErr_Clear();
        Py_XDECREF(m);
        Py_XDECREF(r);
    }

其中,如果发生错误,则将其清除。引用PyErr_Clear()

我用于调试的步骤:

检查是否引发异常

我将功能更改为:

def match_display_hook(self, substitution, matches, longest_match_length):
    try:
        someNonexistentMethod()
    except Exception as e:
        print(e)

然后name 'someNonexistentMethod' is not defined按预期打印(以及所有其他预期的输出)。在此处引发任何其他异常并不会退出命令提示符。

检查打印是否stderr有效

最后,我检查是否可以sys.stderr通过添加以下内容进行打印:

def match_display_hook(self, substitution, matches, longest_match_length):
    print("foobar", file=sys.stderr, flush=True)

foobar按预期打印。

于 2020-11-15T14:55:29.927 回答