Python subprocess问题

4
我希望能在Python中生成一个进程并实现双向通信。当然,Pexpect可以做到这一点,也是我可能会选择的一种方法。但是,它并不完全理想。
我的理想情况是有一种跨平台的通用技术,只涉及标准的Python库。Subprocess非常接近,但是必须等待进程终止才能安全地与其交互,这是不可取的。
查看文档,它确实说有stdin、stdout和stderr文件描述符,我可以直接操作,但有一个大大的警告说“不要这样做”。不幸的是,它并不完全清楚为什么存在这个警告,但从我从谷歌上了解到的信息来看,它与操作系统缓冲有关,如果那些内部缓冲失败时,可能会编写出意外死锁的代码(顺便说一下,任何展示错误方式和正确方式的示例都将不胜感激)。
因此,冒着代码潜在死锁的风险,我认为使用轮询或选择(interactive read)从运行中的进程中读取数据而不杀死它可能很有趣。虽然我失去了(我认为)跨平台的能力,但我喜欢它不需要额外的库的事实。但更重要的是,我想知道这样做是否明智。我还没有尝试过这种方法,但我担心可能会有一些捉摸不透的问题,可能会破坏我的程序。它能行吗?我应该测试什么?
在我的具体情况中,我并不真正关心能否向进程写入数据,只需要反复从中读取数据。此外,我不希望我的进程转储大量文本,所以我希望避免死锁问题,但我想知道这些限制是什么,并能编写一些测试来查看何时会出现问题。

啊!所有我的希望和梦想似乎都被标准输出缓冲所击垮了!为什么操作系统就不能给我那些位呢 :< - Voltaire
你是否在调用一些 Python 没有模块/库的外部第三方工具? - ghostdog74
在某种程度上,是的。幸运的是,这些都在我的控制范围内,因此我在解决方案中有一定的灵活性。如果被调用的脚本刷新了stdout,则我可以与其进行交互式工作。理想情况下,我希望能够使stdout成为一个无缓冲流,但这似乎是不可能的。 - Voltaire
4个回答

3

我最近基于这个模块构建了一个项目,因此我点赞了它。但是,如果进行跨平台开发,会有一些需要注意的地方,例如Windows缺乏fork实现,这意味着在Python中模拟它会产生一些奇怪的副作用。然而,正如发布者所述,它为您提供了与子进程进行良好而轻松的通信的方式,并且总体上使事情变得非常简单。 - jkp

1
我会在一个单独的线程中执行此操作,使用消息队列在线程之间进行通信。在我的情况下,子进程将完成百分比打印到标准输出。我希望主线程能够显示一个漂亮的进度条。
 if sys.platform == 'win32':
        self.shell = False
        self.startupinfo = subprocess.STARTUPINFO()
        self.startupinfo.dwFlags = 0x01
        self.startupinfo.wShowWindow = 0
    else:
        self.shell = True
        self.startupinfo = None

. . .

f = subprocess.Popen( cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, env = env, shell = self.shell, startupinfo = self.startupinfo )
    f.stdin.close()
    line = ''
    while True:
        log.debug('reading')
        c = f.stdout.read(1)

        log.debug(c)

        if len(c) == 0:
            log.info('stdout empty; must be done')
            break;
        if ord(c) == 13:
            continue
        if c == '%':
            # post % complete message to waiting thread.
            line = ''
        else:
            line += c


    log.info('checking for errors')
    errs = f.stderr.readlines()

    if errs:
        prettyErrs = 'Reported Errors: '
        for i in errs:
            prettyErrs += i.rstrip('\n')

        log.warn( prettyErrs )
        #post errors to waiting thread
    else:
        print 'done'        
    return

我在周末期间处理了这个问题,那个已经接近我想到的解决方案了。这与轮询数据是否准备好无关,而与stdio作为缓冲流有关。在我特定的情况下,我可以假设stdout会被正在执行的脚本定期刷新。因此,简单的阻塞readline在单独的线程中运行就可以满足我所有的需求。如果有人有如何取消stdio缓冲的工作示例,我将非常感兴趣。 - Voltaire

0
简短的回答是,如果没有将这个概念设计到你的系统中,那么就没有一个好的跨平台进程管理系统。特别是在标准库中。即使各种Unix版本也有自己的兼容性问题。
你最好的选择是使用适当的事件处理为所有进程进行仪器化,以注意来自任何IPC系统的事件,该IPC系统在任何平台上都能正常工作。命名管道将是你所描述的问题的一般路线,但每个平台上都会有实现差异。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接