为什么Python的subprocess.call这样实现?
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 个回答
如果你只是想运行一个命令,然后通过它的退出状态来判断这个命令是成功还是失败,那你就不需要通过管道来和它交流。这就是subprocess.call()方法的方便之处。subprocess模块里还有其他一些方便的函数,它们可以高效地处理很多常见的使用Popen对象的情况。
如果你需要把子进程的标准输出(stdout)或标准错误(stderr)发送到其他地方,那就不要使用call(),而是使用Popen对象,并按照文档中的说明与它进行交流。
我花了一些时间查看了PEP-324,这个文档介绍了子进程模块,想弄清楚其中的设计决策,但我觉得其实答案很简单:
其实没有必要在subprocess.call
中传入stdout=PIPE
或stderr=PIPE
,所以它可能会出现死锁的问题也无所谓。
唯一需要在subprocess.Popen
中传入stdout=PIPE
或stderr=PIPE
的原因,是为了可以把Popen实例的stdout
和stderr
当作文件对象来使用。因为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文件中。