首页 > 解决方案 > 为 Python 的 Cmd 类输入流使用协程

问题描述

我面临的问题是:

我有一个基于Python 的 Cmd 类构建的自定义命令解释器。我为它提供了自定义标准输入和标准输出。出于这个问题的目的,它看起来像这样:

import cmd
import sys

class CustomStream(object):
    def readline(self):
        return sys.stdin.readline()
    def write(self, msg):
        sys.stdout.write(msg)
    def flush(self):
        pass

class MyShell(cmd.Cmd):
    def do_stuff(self, args):
        print("Getting things done...")
    def do_exit(self, args):
        return True

stream = CustomStream()
shell = MyShell(stdin=stream, stdout=stream)
shell.use_rawinput = False 
shell.cmdloop()

Cmd需要从用户那里读取时,它会这样做:

line = self.stdin.readline()

我想使用基于asyncio. 我的 SSH 代码很像Simple Server 示例,它读取类似 stdin 的接口,如下所示(注意await关键字):

line_from_client = (await self._process.stdin.readline()).rstrip('\n')

我尝试了很多东西,但我无法将 SSH 代码输入到 Cmd 对标准输入的期望中。我必须怎么做才能让我的CustomStream对象在内部使用 asyncio/coroutines,同时提供一个老式的单线程接口MyShell

标签: pythonsshasync-awaitstdin

解决方案


解决方案是修补该cmdloop方法以使其能够感知异步。

此代码是 Python 的 3.7.2 Cmd 类 cmdloop 函数的副本,如果您设置

  • raw_input调成True
  • 放在awaitreadline前面

此代码的结果(aoicmd.py 可作为 gist 获得):

async def adapter_cmdloop(self, intro=None):
    """Repeatedly issue a prompt, accept input, parse an initial prefix
    off the received input, and dispatch to action methods, passing them
    the remainder of the line as argument.

    """
    self.preloop()

    #This is the same code as the Python 3.7.2 Cmd class, with the
    #following changes
    #  - Remove dead code caused by forcing use_rawinput=False.
    #  - Added a readline in front of readline()
    if intro is not None:
        self.intro = intro
    if self.intro:
        self.stdout.write(str(self.intro)+"\n")
    stop = None
    while not stop:
        if self.cmdqueue:
            line = self.cmdqueue.pop(0)
        else:
            self.stdout.write(self.prompt)
            self.stdout.flush()
            line = await self.stdin.readline()
            if not len(line):
                line = 'EOF'
            else:
                line = line.rstrip('\r\n')
        line = self.precmd(line)
        stop = self.onecmd(line)
        stop = self.postcmd(stop, line)
    self.postloop()

在需要使用 Cmd 派生类的地方,例如MyShell,创建一个MyAsyncShell在运行时调用的新类:

#Patch the shell with async aware cmdloop
MyAsyncShell = type('MyAsyncSHell', (MyShell,), {
    'cmdloop' :aiocmd.adapter_cmdloop,
    'use_rawinput':False,
})

实施write并且flush如您所见,但您的 readline 应如下所示:

async def readline(self):
    return await my_implementation_of_readline()

推荐阅读