【问题标题】:Why does Python sometimes throw ValueErrors in this code?为什么 Python 有时会在这段代码中抛出 ValueErrors?
【发布时间】:2016-02-15 20:23:38
【问题描述】:

我有一些 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 次中的 1 次,非常近似),我会收到如下错误:

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 文档似乎没有涵盖这种情况。

【问题讨论】:

  • 你在使用多个线程吗?否则,它看起来像一个错误。你能创建一个虚拟的child.py 来传递someargs 来重现这个问题吗?
  • 这是在 3.6.0 中重现此问题的代码:` import subprocess import time po = subprocess.Popen(['/bin/ls'], stdout=subprocess.PIPE, stderr=subprocess. STDOUT) time.sleep(1) out, err = po.communicate(timeout=1) out, err = po.communicate(timeout=1)`

标签: python python-3.x io subprocess


【解决方案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报告问题

【讨论】:

  • 非常有趣。不知道 Python 3.5 的改进。
【解决方案2】:

改进的答案

使用空字节对象作为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()

【讨论】:

  • Mike,这不会丢弃在“try”块期间检索到这些缓冲区中的任何信息吗?
  • 你应该使用stdout, stderr = b'', b'',而不是io.BytesIO()
  • 感谢您的提示。添加了一项改进。
  • 需要my_process.kill() 吗?文档说:“如果超时到期,子进程将被杀死并等待。”
  • 这是subprocess.run() 的帮助。 subprocessPopen() 的帮助说:“如果超时到期,子进程不会被杀死,因此为了正确清理,行为良好的应用程序应该杀死子进程并完成通信。”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多