Verwenden des Moduls ‘subprocess’ mit Timeout

Lesezeit: 7 Minuten

Hier ist der Python-Code, um einen beliebigen Befehl auszuführen, der seinen zurückgibt stdout Daten, oder lösen Sie eine Ausnahme bei Exit-Codes ungleich Null aus:

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

communicate wird verwendet, um auf das Beenden des Prozesses zu warten:

stdoutdata, stderrdata = proc.communicate()

Das subprocess Das Modul unterstützt kein Timeout – die Fähigkeit, einen Prozess zu beenden, der länger als X Sekunden läuft – daher communicate kann ewig dauern.

Was ist der einfachste Möglichkeit, Timeouts in einem Python-Programm zu implementieren, das unter Windows und Linux ausgeführt werden soll?

  • Ein verwandter Eintrag in der Python-Problemverfolgung: bugs.python.org/issue5673

    – Sridhar Ratnakumar

    28. Juli 2009 um 1:54 Uhr

  • Verwenden pypi.python.org/pypi/subprocess32 für Python2.x. Es ist ein Backport von Python 3.x. Es hat das Timeout-Argument für call() und wait().

    – Güttli

    18. November 2013 um 12:28 Uhr


  • pypi.python.org/pypi/subprocess32 funktioniert nicht unter Windows 🙁

    – AdrianX

    9. November 2015 um 1:13 Uhr

Ich weiß nicht viel über die Details auf niedriger Ebene; aber angesichts der Tatsache, dass die API in Python 2.6 die Möglichkeit bietet, auf Threads zu warten und Prozesse zu beenden, was ist mit der Ausführung des Prozesses in einem separaten Thread?

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)

Die Ausgabe dieses Snippets in meinem Computer ist:

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

wo zu sehen ist, dass bei der ersten Ausführung der Prozess korrekt beendet wurde (Rückgabecode 0), während bei der zweiten der Prozess beendet wurde (Rückgabecode -15).

Ich habe nicht in Windows getestet; Aber abgesehen von der Aktualisierung des Beispielbefehls denke ich, dass es funktionieren sollte, da ich in der Dokumentation nichts gefunden habe, was besagt, dass thread.join oder process.terminate nicht unterstützt werden.

  • +1 Für Plattformunabhängigkeit. Ich habe dies sowohl unter Linux als auch unter Windows 7 (Cygwin und Plain Windows Python) ausgeführt – funktioniert in allen drei Fällen wie erwartet.

    – phoji

    17. Februar 2011 um 0:27 Uhr

  • Ich habe Ihren Code ein wenig modifiziert, um native Popen-Kwargs übergeben und auf den Punkt bringen zu können. Es ist jetzt bereit, vielseitig einsetzbar zu sein; gist.github.com/1306188

    – Kirpit

    9. November 2011 um 13:07 Uhr

  • Für alle, die das Problem haben, das @redice hatte, kann diese Frage hilfreich sein. Kurz gesagt, wenn Sie Shell=True verwenden, wird die Shell zum Kindprozess, der beendet wird, und sein Befehl (Kind des Kindprozesses) lebt weiter.

    – Anson

    19. März 2013 um 0:11 Uhr


  • Diese Antwort bietet nicht die gleiche Funktionalität wie das Original, da stdout nicht zurückgegeben wird.

    – stephenbez

    17. Dezember 2013 um 16:39 Uhr

  • thread.is_alive kann zu einer Racebedingung führen. Sehen ostricher.com/2015/01/python-subprocess-with-timeout

    – ChaimKut

    7. Mai 2015 um 12:56 Uhr

  • +1 für einfache tragbare Lösung. Du brauchst nicht lambda: t = Timer(timeout, proc.kill)

    – jfs

    5. April 2014 um 21:43 Uhr

  • +1 Dies sollte die akzeptierte Antwort sein, da die Art und Weise, wie der Prozess gestartet wird, nicht geändert werden muss.

    – Dave Branton

    28. Mai 2015 um 22:18 Uhr

  • Warum braucht es das Lambda? Könnte die gebundene Methode p.kill dort nicht ohne das Lambda verwendet werden?

    – Danny Staple

    5. August 2015 um 16:10 Uhr

  • // , Wären Sie bereit, ein Beispiel für die Verwendung von this einzufügen?

    – Nathan Basanesisch

    2. September 2015 um 0:27 Uhr

  • @tuk timer.isAlive() Vor timer.cancel() bedeutet, dass es normal endete

    – Karl

    28. April 2020 um 15:51 Uhr

Wenn Sie Unix verwenden,

import signal
  ...
class Alarm(Exception):
    pass

def alarm_handler(signum, frame):
    raise Alarm

signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(5*60)  # 5 minutes
try:
    stdoutdata, stderrdata = proc.communicate()
    signal.alarm(0)  # reset the alarm
except Alarm:
    print "Oops, taking too long!"
    # whatever else

  • Nun, ich interessiere mich für eine plattformübergreifende Lösung, die zumindest unter Win/Linux/Mac funktioniert.

    – Sridhar Ratnakumar

    28. Juli 2009 um 1:52 Uhr

  • Ich mag diesen Unix-basierten Ansatz. Idealerweise würde man dies mit einem Windows-spezifischen Ansatz (unter Verwendung von CreateProcess und Jobs) kombinieren. Aber im Moment ist die folgende Lösung einfach, leicht und funktioniert bisher.

    – Sridhar Ratnakumar

    29. Juli 2009 um 19:43 Uhr

  • Ich habe eine tragbare Lösung hinzugefügt, siehe meine Antwort

    – Flybywire

    13. Oktober 2009 um 8:16 Uhr

  • Diese Lösung würde funktionieren nur wenn signal.signal(signal.SIGALARM, alarm_handler) wird vom Haupt-Thread aufgerufen. Siehe die Dokumentation für signal

    – flüchtige Leere

    19. Dezember 2009 um 5:58 Uhr

  • Leider habe ich beim Ausführen (unter Linux) im Kontext eines Apache-Moduls (wie mod_python, mod_perl oder mod_php) festgestellt, dass die Verwendung von Signalen und Alarmen nicht zulässig ist (vermutlich, weil sie die eigene IPC-Logik von Apache stören). Um also das Ziel zu erreichen, einen Befehl zu timen, war ich gezwungen, “Elternschleifen” zu schreiben, die einen untergeordneten Prozess starten und dann in einer “Schlaf”-Schleife sitzen und die Uhr beobachten (und möglicherweise auch die Ausgabe des Kindes überwachen).

    – Petrus

    29. Juli 2011 um 0:54 Uhr

Hier ist die Lösung von Alex Martelli als Modul mit ordnungsgemäßer Prozessbeseitigung. Die anderen Ansätze funktionieren nicht, weil sie proc.communicate() nicht verwenden. Wenn Sie also einen Prozess haben, der viele Ausgaben erzeugt, füllt er seinen Ausgabepuffer und blockiert dann, bis Sie etwas daraus lesen.

from os import kill
from signal import alarm, signal, SIGALRM, SIGKILL
from subprocess import PIPE, Popen

def run(args, cwd = None, shell = False, kill_tree = True, timeout = -1, env = None):
    '''
    Run a command with a timeout after which it will be forcibly
    killed.
    '''
    class Alarm(Exception):
        pass
    def alarm_handler(signum, frame):
        raise Alarm
    p = Popen(args, shell = shell, cwd = cwd, stdout = PIPE, stderr = PIPE, env = env)
    if timeout != -1:
        signal(SIGALRM, alarm_handler)
        alarm(timeout)
    try:
        stdout, stderr = p.communicate()
        if timeout != -1:
            alarm(0)
    except Alarm:
        pids = [p.pid]
        if kill_tree:
            pids.extend(get_process_children(p.pid))
        for pid in pids:
            # process might have died before getting to this line
            # so wrap to avoid OSError: no such process
            try: 
                kill(pid, SIGKILL)
            except OSError:
                pass
        return -9, '', ''
    return p.returncode, stdout, stderr

def get_process_children(pid):
    p = Popen('ps --no-headers -o pid --ppid %d' % pid, shell = True,
              stdout = PIPE, stderr = PIPE)
    stdout, stderr = p.communicate()
    return [int(p) for p in stdout.split()]

if __name__ == '__main__':
    print run('find /', shell = True, timeout = 3)
    print run('find', shell = True)

  • Nun, ich interessiere mich für eine plattformübergreifende Lösung, die zumindest unter Win/Linux/Mac funktioniert.

    – Sridhar Ratnakumar

    28. Juli 2009 um 1:52 Uhr

  • Ich mag diesen Unix-basierten Ansatz. Idealerweise würde man dies mit einem Windows-spezifischen Ansatz (unter Verwendung von CreateProcess und Jobs) kombinieren. Aber im Moment ist die folgende Lösung einfach, leicht und funktioniert bisher.

    – Sridhar Ratnakumar

    29. Juli 2009 um 19:43 Uhr

  • Ich habe eine tragbare Lösung hinzugefügt, siehe meine Antwort

    – Flybywire

    13. Oktober 2009 um 8:16 Uhr

  • Diese Lösung würde funktionieren nur wenn signal.signal(signal.SIGALARM, alarm_handler) wird vom Haupt-Thread aufgerufen. Siehe die Dokumentation für signal

    – flüchtige Leere

    19. Dezember 2009 um 5:58 Uhr

  • Leider habe ich beim Ausführen (unter Linux) im Kontext eines Apache-Moduls (wie mod_python, mod_perl oder mod_php) festgestellt, dass die Verwendung von Signalen und Alarmen nicht zulässig ist (vermutlich, weil sie die eigene IPC-Logik von Apache stören). Um also das Ziel zu erreichen, einen Befehl zu timen, war ich gezwungen, “Elternschleifen” zu schreiben, die einen untergeordneten Prozess starten und dann in einer “Schlaf”-Schleife sitzen und die Uhr beobachten (und möglicherweise auch die Ausgabe des Kindes überwachen).

    – Petrus

    29. Juli 2011 um 0:54 Uhr

timeout wird jetzt unterstützt durch call() und communicate() im subprocess-Modul (ab Python3.3):

import subprocess

subprocess.call("command", timeout=20, shell=True)

Dadurch wird der Befehl aufgerufen und die Ausnahme ausgelöst

subprocess.TimeoutExpired

wenn der Befehl nach 20 Sekunden nicht beendet ist.

Sie können dann die Ausnahme behandeln, um Ihren Code fortzusetzen, etwa so:

try:
    subprocess.call("command", timeout=20, shell=True)
except subprocess.TimeoutExpired:
    # insert code here

Hoffe das hilft.

  • Es gibt eine vorhandene Antwort, die die erwähnt timeout Parameter. Obwohl es nicht schaden würde, es noch einmal zu erwähnen.

    – jfs

    23. Februar 2015 um 2:43 Uhr

  • // , ich denke, OP sucht nach einer Lösung für das ältere Python.

    – Nathan Basanesisch

    2. September 2015 um 0:29 Uhr

1094770cookie-checkVerwenden des Moduls ‘subprocess’ mit Timeout

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy