在替换sys.stdout时,Python readline在cmd.Cmd中的Tab补全

5 投票
1 回答
2016 浏览
提问于 2025-04-17 09:45

目前,我有一个应用程序,它使用cmd.Cmd模块来实现命令行界面,自动补全功能运行得很好。

现在,我想把sys.stdout替换成另一个对象(比如说,为了捕捉正在写入的内容)。

理论上,下面这段代码应该是完全透明的,因为对Std对象的每次获取或设置操作都会被重定向到实际的sys.__stdout__

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)

sys.stdout = Std()

例如,sys.stdout.fileno()仍然会输出1。不过,Cmd.cmd的自动补全功能现在不再工作了……

好吧,让我们从文件继承一下。(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)

sys.stdout = Std()

现在我得到了:

Traceback (most recent call last):
  File "./shell.py", line 61, in <module>
    print '#1'
ValueError: I/O operation on closed file

不过,下面这个断言并没有失败:

assert not sys.stdout.closed

不知怎么的,我猜Python在某些地方过于优化,绕过了Std.write。

我该怎么做才能替换stdout,同时又不失去readline的支持呢……?

Jonathan

-编辑-

我也在尝试替换sys.stdin。把它传给cmd.Cmd并不奏效,因为它使用raw_input来支持readline,而Python的raw_input不接受文件描述符。它会回退到分配给sys.stdin的任何pty。

当我通过os.openpty()创建一个新的pty,并把这对pty分配给sys.stdin/out时,通过这个pty的readline自动补全功能工作得很好,但再次强调,当它被包装在一个代理对象中时,虽然可以工作,但没有自动补全。

我在尝试理解readline的源代码,但这并不容易:http://svn.python.org/projects/python/branches/release25-maint/Modules/readline.c

1 个回答

1

我不太清楚为什么替换 sys.stdout 不管用,但无论如何,你可以通过给 cmd.Cmd 的构造函数传入你自己的文件对象来解决你眼前的问题。

这里有一个示例脚本(部分内容借鉴自 PyMOTW),展示了如何实现按下 Tab 键自动补全:

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 属性访问。

撰写回答