为什么Python有时会在此代码中抛出ValueErrors?

时间:2015-11-14 08:27:39

标签: python python-3.x io subprocess

我有一些看起来像这样的Python 3.5代码:

try:
    my_process = Popen(someargs, stdin=None, stdout=PIPE, stderr=PIPE)
    stdout, stderr = my_process.communicate(timeout=10)
    my_process.wait()
except TimeoutExpired:
    my_process.kill()
    stdout, stderr = my_process.communicate()

我试图遵循the python subprocess documentation here中描述的原则,即在TimeoutError的情况下,我应该手动终止进程,然后完成通信。

原则上听起来很好,但是周期性地(可能每50次一次,非常近似),我得到这样的错误:

Traceback (most recent call last):
  File "/Users/xyz/myprogram/myprogram", line 125, in <module>
    stdout, stderr = my_process.communicate()
  File "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py", line 1068, in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
  File "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py", line 1689, in _communicate
    selector.register(self.stdout, selectors.EVENT_READ)
  File "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/selectors.py", line 342, in register
    key = super().register(fileobj, events, data)
  File "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/selectors.py", line 228, in register
    key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)
  File "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/selectors.py", line 215, in _fileobj_lookup
    return _fileobj_to_fd(fileobj)
  File "/usr/local/Cellar/python3/3.5.0/Frameworks/Python.framework/Versions/3.5/lib/python3.5/selectors.py", line 39, in _fileobj_to_fd
    "{!r}".format(fileobj)) from None
ValueError: Invalid file object: <_io.BufferedReader name=5>

第125行是我案例中的第二行communicate()

这似乎失败了,因为流程对象底层的某些流已关闭或终止 - 有时可能会发生在kill()communicate()之间的情况。但如果是这样,我应该采用一种优雅的方式处理这个问题吗? Python文档似乎不包括这种情况。

2 个答案:

答案 0 :(得分:1)

改进答案

使用空字节对象作为stdoutstderr的返回值可能是一种解决方案。 my_process.communicate()为您读取管道。所以最好不要在这里使用文件对象:

try:
    my_process = Popen(someargs, stdin=None, stdout=PIPE, stderr=PIPE)
    stdout, stderr = my_process.communicate(timeout=10)
    my_process.wait()
except TimeoutExpired:
    my_process.kill()
    try:
        stdout, stderr = my_process.communicate()
    except ValueError:
        stdout = b''
        stderr = b''

原始答案

stdoutstderr创建空文件对象可能是一个解决方案:

import io

try:
    my_process = Popen(someargs, stdin=None, stdout=PIPE, stderr=PIPE)
    stdout, stderr = my_process.communicate(timeout=10)
    my_process.wait()
except TimeoutExpired:
    my_process.kill()
    try:
        stdout, stderr = my_process.communicate()
    except ValueError:
        stdout = io.BytesIO()
        stderr = io.BytesIO()

答案 1 :(得分:1)

要解决my_process.communicate()在异常处理程序中引发ValueError,您可以直接从流中读取(更简单的代码路径 - 在一般情况下不要使用它):

from subprocess import Popen, PIPE, TimeoutExpired

with Popen(cmd, stdout=PIPE, stderr=PIPE) as process:
    try:
        stdout, stderr = process.communicate(timeout=10)
    except TimeoutExpired:
        process.kill()
        stdout = process.stdout.read() # the process is dead, no deadlock
        stderr = process.stderr.read()

在Python 3.5上,您可以使用subprocess.run()

import subprocess
from subprocess import PIPE, TimeoutExpired

try:
    result = subprocess.run(cmd, timeout=10, stdout=PIPE, stderr=PIPE)
except TimeoutExpired as e:
    result = e
stdout, stderr = result.stdout, result.stderr

虽然它以与代码相同的方式处理TimeoutExpired,因此无论如何都可以获得ValueError。如果您使用此代码获得ValueError;在http://bugs.python.org

报告此问题