在 Python 中,readline
模块提供了一个交互式的命令行输入接口,其中的 Tab 补全是指用户在输入时按下 Tab 键,系统会自动尝试完成当前输入的命令或路径。
Tab 补全的主要功能是帮助用户更快速、更准确地输入命令或路径,尤其是当有很多可能的选项时。下面我将用详细的步骤来说明 Tab 补全的具体作用:
1、问题背景
在一个使用 Python 的应用程序中,我们使用了 cmd.Cmd
模块来构建命令行界面,而它的选项卡自动补全功能(Tab-completion)也能正常运行。现在,我们想要在原先的基础上替换 sys.stdout
对象,以便捕获正在被写入的信息。
为了替换 sys.stdout
对象,我们创建了以下 Std 类:
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
类的任何 get
或 set
操作重定向到真实的 sys.__stdout__
对象。比如,调用 sys.stdout.fileno()
仍然会返回文件描述符 1。
然而,当替换 sys.stdout
对象之后,cmd.Cmd
的自动补全功能却不再起作用了。为了解决这个问题,我们尝试使用 file
进行继承(stdout
本身是一个文件对象)。
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
构造函数。
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 补全功能,提高用户的输入效率。
通过上面的案例是不是觉得很神奇?通过小小的改动居然可以达到意想不到的效果,就问牛不牛。当然如果有任何技术性的疑惑,可以评论区留言一起讨论。