使用'subprocess'模块和超时

395 投票
32 回答
428711 浏览
提问于 2025-04-15 13:12

下面是用Python写的代码,可以执行一个命令,并返回它的输出数据。如果命令执行出错(返回的结果不是0),就会抛出一个异常:

proc = subprocess.Popen(
    cmd,
    stderr=subprocess.STDOUT,  # Merge stdout and stderr
    stdout=subprocess.PIPE,
    shell=True)

communicate这个函数是用来等待进程结束的:

stdoutdata, stderrdata = proc.communicate()

不过,subprocess这个模块不支持超时功能,也就是说它不能自动结束一个运行超过设定时间的进程。因此,communicate可能会一直运行下去。

那么,有什么最简单的方法可以在一个打算在Windows和Linux上运行的Python程序中实现超时呢?

32 个回答

168

jcollado的回答可以用更简单的方式来解释,使用的是threading.Timer这个类:

import shlex
from subprocess import Popen, PIPE
from threading import Timer

def run(cmd, timeout_sec):
    proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
    timer = Timer(timeout_sec, proc.kill)
    try:
        timer.start()
        stdout, stderr = proc.communicate()
    finally:
        timer.cancel()

# Examples: both take 1 second
run("sleep 1", 5)  # process ends normally at 1 second
run("sleep 5", 1)  # timeout happens at 1 second
216

我对底层的细节了解不多,不过在 Python 2.6 中,API 提供了等待线程和终止进程的功能。那么,能不能把进程放在一个单独的线程中运行呢?

import subprocess, threading

class Command(object):
    def __init__(self, cmd):
        self.cmd = cmd
        self.process = None

    def run(self, timeout):
        def target():
            print 'Thread started'
            self.process = subprocess.Popen(self.cmd, shell=True)
            self.process.communicate()
            print 'Thread finished'

        thread = threading.Thread(target=target)
        thread.start()

        thread.join(timeout)
        if thread.is_alive():
            print 'Terminating process'
            self.process.terminate()
            thread.join()
        print self.process.returncode

command = Command("echo 'Process started'; sleep 2; echo 'Process finished'")
command.run(timeout=3)
command.run(timeout=1)

在我机器上运行这段代码的输出是:

Thread started
Process started
Process finished
Thread finished
0
Thread started
Process started
Terminating process
Thread finished
-15

可以看到,在第一次执行时,进程正确结束了(返回码是 0),而在第二次执行时,进程被终止了(返回码是 -15)。

我还没有在 Windows 上测试过;不过,除了更新示例命令外,我觉得应该可以运行,因为我在文档中没有找到任何说明说 thread.join 或 process.terminate 不被支持。

235

在 Python 3.3 及以上版本中:

from subprocess import STDOUT, check_output

output = check_output(cmd, stderr=STDOUT, timeout=seconds)

output 是一个字节字符串,里面包含了命令执行后合并的标准输出和错误输出的数据。

check_output 在命令执行返回非零状态时会抛出 CalledProcessError 错误,这和 proc.communicate() 方法的表现不同。

我去掉了 shell=True,因为它通常是多余的。如果你的命令确实需要它,你可以再加上去。如果你加上 shell=True,也就是说如果子进程自己再启动其他进程,check_output() 可能会比你设定的超时时间晚得多才返回结果,具体可以参考 子进程超时失败

在 Python 2.x 中,可以通过 subprocess32 这个库来使用 3.2 及以上版本的子进程模块的超时功能。

撰写回答