为什么Python的subprocess.call这样实现?

13 投票
2 回答
5779 浏览
提问于 2025-04-16 13:00

subprocess模块里有个很方便的函数叫做call,在2.6和3.1版本中都是这样实现的:

def call(*popenargs, **kwargs):
    return Popen(*popenargs, **kwargs).wait()

这个函数的文档上有个红色警告,内容是:

警告:和Popen.wait()一样,当使用stdout=PIPE和/或stderr=PIPE时,如果子进程输出的数据太多,会导致死锁,等着操作系统的管道缓冲区接受更多数据。

Popen.wait()的文档建议在这种情况下使用Popen.communicate()。那么,为什么call不直接像下面这样实现呢?这样就可以去掉这个烦人的警告,也能消除这些愚蠢的限制,让标准库更好用。

def call(*args, **kwargs):
    input = kwargs.pop("input", None)
    p = Popen(*args, **kwargs)
    p.communicate(input)
    return p.returncode

我相信一定有原因。我是不是漏掉了什么?

2 个回答

2

如果你只是想运行一个命令,然后通过它的退出状态来判断这个命令是成功还是失败,那你就不需要通过管道来和它交流。这就是subprocess.call()方法的方便之处。subprocess模块里还有其他一些方便的函数,它们可以高效地处理很多常见的使用Popen对象的情况。

如果你需要把子进程的标准输出(stdout)或标准错误(stderr)发送到其他地方,那就不要使用call(),而是使用Popen对象,并按照文档中的说明与它进行交流。

8

我花了一些时间查看了PEP-324,这个文档介绍了子进程模块,想弄清楚其中的设计决策,但我觉得其实答案很简单:

其实没有必要subprocess.call中传入stdout=PIPEstderr=PIPE,所以它可能会出现死锁的问题也无所谓。

唯一需要在subprocess.Popen中传入stdout=PIPEstderr=PIPE的原因,是为了可以把Popen实例的stdoutstderr当作文件对象来使用。因为subprocess.call根本不让你看到Popen实例,所以PIPE选项就没什么意义了。

使用Popen.communicate可能会有额外的开销(因为它会创建额外的线程来监控管道以避免死锁),而在这种情况下并没有好处,所以没有必要使用它。

补充:如果你想丢弃输出,直接这样做可能更好:

# option 1
with open(os.devnull, 'w') as dev_null:
    subprocess.call(['command'], stdout=dev_null, stderr=dev_null)

# option 2
subprocess.call(['command >& /dev/null'], shell=True)

而不是指示子进程捕获所有输出到你根本不打算使用的PIPE文件中。

撰写回答