【问题标题】:Why don't I see errors from readline.set_completion_display_matches_hook?为什么我看不到 readline.set_completion_display_matches_hook 的错误?
【发布时间】:2020-04-22 13:56:50
【问题描述】:

考虑这段代码:

#!/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()

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

【问题讨论】:

    标签: python error-handling readline error-suppression python-cmd


    【解决方案1】:

    TL;DR

    看起来readline C-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

    【讨论】:

      【解决方案2】:

      为什么?

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

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

      请参阅rlcompleter source code 了解基本原理:

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

      解决方法

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

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-12-30
        • 2014-12-13
        • 1970-01-01
        • 2017-06-20
        • 2012-03-23
        • 2021-08-28
        • 1970-01-01
        • 2021-07-21
        相关资源
        最近更新 更多