如何绕过Python readline的Tab-补全

2024-02-04 10:25:02 浏览数 (3)

在 Python 中,readline 模块提供了一个交互式的命令行输入接口,其中的 Tab 补全是指用户在输入时按下 Tab 键,系统会自动尝试完成当前输入的命令或路径。

Tab 补全的主要功能是帮助用户更快速、更准确地输入命令或路径,尤其是当有很多可能的选项时。下面我将用详细的步骤来说明 Tab 补全的具体作用:

1、问题背景

在一个使用 Python 的应用程序中,我们使用了 cmd.Cmd 模块来构建命令行界面,而它的选项卡自动补全功能(Tab-completion)也能正常运行。现在,我们想要在原先的基础上替换 sys.stdout 对象,以便捕获正在被写入的信息。

为了替换 sys.stdout 对象,我们创建了以下 Std 类:

代码语言:javascript复制
class Std(object):
    def __getattribute__(self, name):
        if name in ('__getattribute__', '__setattr__'):
            return object.__getattribute__(self, name)
        else:
            return getattr(sys.__stdout__, name)
​
    def __setattr__(self, name, value):
        setattr(sys.__stdout__, name, value)

通过这个类我们可以把对 Std 类的任何 getset 操作重定向到真实的 sys.__stdout__ 对象。比如,调用 sys.stdout.fileno() 仍然会返回文件描述符 1。

然而,当替换 sys.stdout 对象之后,cmd.Cmd 的自动补全功能却不再起作用了。为了解决这个问题,我们尝试使用 file 进行继承(stdout 本身是一个文件对象)。

代码语言:javascript复制
class Std(file):
    def __init__(self):
        pass
    def __getattribute__(self, name):
        if name in ('__getattribute__', '__setattr__'):
            return object.__getattribute__(self, name)
        else:
            return getattr(sys.__stdout__, name)
​
    def __setattr__(self, name, value):
        setattr(sys.__stdout__, name, value)

但是,这次尝试却又导致了以下错误:

ValueError: I/O operation on closed file

于是,我们尝试直接阅读 Readline 的源码来理解其中的问题。然而,Readline 的源码并不容易理解。

2、解决方案

虽然我们并不完全知道为什么替换 sys.stdout 对象会导致问题,但有一种方法可以解决这个问题:直接将我们自己的文件对象传给 cmd.Cmd 构造函数。

代码语言:javascript复制
import sys, cmd
​
class Std(object):
    def __getattribute__(self, name):
        if name in ('__getattribute__', '__setattr__'):
            return object.__getattribute__(self, name)
        else:
            return getattr(sys.__stdout__, name)
​
    def __setattr__(self, name, value):
        setattr(sys.__stdout__, name, value)
​
class HelloWorld(cmd.Cmd):
    FRIENDS = [ 'Alice', 'Adam', 'Barbara', 'Bob' ]
​
    def do_greet(self, person):
        "Greet the person"
        if person and person in self.FRIENDS:
            greeting = 'hi, %s!' % person
        elif person:
            greeting = "hello, "   person
        else:
            greeting = 'hello'
        print greeting
​
    def complete_greet(self, text, line, begidx, endidx):
        if not text:
            completions = self.FRIENDS[:]
        else:
            completions = [f for f in self.FRIENDS
                           if f.startswith(text)]
        return completions
​
    def do_EOF(self, line):
        return True
​
if __name__ == '__main__':
​
    HelloWorld(stdout=Std()).cmdloop()

这种做法更好,因为它能确保只捕获 cmd 实例所产生的输出。值得注意的是,我们传入的文件对象也能作为 cmd 实例中的一个 stdout 属性使用。

在上述示例中,当用户输入部分水果名称(例如 'app')并按下 Tab 键时,readline 将自动补全为匹配的选项,如 'apple'。如果有多个匹配项,用户可以继续按下 Tab 键以在可选项之间进行循环。

通过设置 readline 的补全函数和绑定 Tab 键的行为,可以在交互式 Python 环境中实现类似于 Bash 等 shell 的 Tab 补全功能,提高用户的输入效率。

通过上面的案例是不是觉得很神奇?通过小小的改动居然可以达到意想不到的效果,就问牛不牛。当然如果有任何技术性的疑惑,可以评论区留言一起讨论。

0 人点赞