2010-02-17 14 views
116

Ich habe ein Shell-Skript, das eine Textdatei mit URLs durchläuft, die ich besuchen möchte und Screenshots von.Timeout-Funktion, wenn es zu lange dauert zu beenden

All dies ist getan und einfach. Das Skript initialisiert eine Klasse, die bei der Ausführung einen Screenshot von jeder Site in der Liste erstellt. Einige Websites benötigen sehr viel Zeit zum Laden und manche werden möglicherweise überhaupt nicht geladen. Also möchte ich die Screengrabber-Funktion in ein Timeout-Skript einbinden, damit die Funktion False zurückgibt, wenn sie nicht innerhalb von 10 Sekunden beendet werden konnte.

Ich bin zufrieden mit der einfachsten Lösung möglich, möglicherweise Einstellung eines asynchronen Timers, der nach 10 Sekunden, egal was tatsächlich in der Funktion geschieht False zurückgegeben wird?

+0

Für alle faul Leute, die lieben, Bibliotheken zu verwenden: https://pypi.python.org/pypi/timeout-decorator – guettli

Antwort

186

Der Vorgang zum Zeitablauf einer Operation ist in der Dokumentation für signal beschrieben.

Die Grundidee besteht darin, Signalhandler zu verwenden, um einen Alarm für einige Zeitintervalle zu setzen und eine Ausnahme auszulösen, sobald der Timer abgelaufen ist.

Beachten Sie, dass dies nur unter UNIX funktioniert.

Hier ist eine Implementierung, die einen Dekorateur erstellt (speichern Sie den folgenden Code als timeout.py).

from functools import wraps 
import errno 
import os 
import signal 

class TimeoutError(Exception): 
    pass 

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)): 
    def decorator(func): 
     def _handle_timeout(signum, frame): 
      raise TimeoutError(error_message) 

     def wrapper(*args, **kwargs): 
      signal.signal(signal.SIGALRM, _handle_timeout) 
      signal.alarm(seconds) 
      try: 
       result = func(*args, **kwargs) 
      finally: 
       signal.alarm(0) 
      return result 

     return wraps(func)(wrapper) 

    return decorator 

Dies schafft einen Dekorateur @timeout genannt, die auf irgendwelchen lange Lauf Funktionen angewendet werden können.

Also, im Anwendungscode, können Sie den Dekorateur wie so verwenden können:

from timeout import timeout 

# Timeout a long running function with the default expiry of 10 seconds. 
@timeout 
def long_running_function1(): 
    ... 

# Timeout after 5 seconds 
@timeout(5) 
def long_running_function2(): 
    ... 

# Timeout after 30 seconds, with the error "Connection timed out" 
@timeout(30, os.strerror(errno.ETIMEDOUT)) 
def long_running_function3(): 
    ... 
+50

Beachten Sie, dass dies nicht Thread-sicher ist: Wenn Sie Multithreading verwenden, wird das Signal von einem zufälligen Thread abgefangen. Für Single-Thread-Programme ist dies jedoch die einfachste Lösung. – Wim

+1

Schön. Es wird auch empfohlen, die Funktion 'wrapper' mit' @ functools.wraps (func) ' – shx2

+6

zu dekorieren, da nach dem ersten" @timeout "keine Parens mehr vorhanden sind. Es sollte '@timeout() def ...' lesen. –

116

ich David Antwort mit der with Anweisung neu geschrieben, es erlaubt Ihnen, dies zu tun tun:

with timeout(seconds=3): 
    time.sleep(4) 

Welche wird einen TimeoutError auslösen.

Der Code ist immer noch mit signal und damit nur UNIX: statt copy + Einfügen von Code-Schnipsel von Stackoverflow

import signal 

class timeout: 
    def __init__(self, seconds=1, error_message='Timeout'): 
     self.seconds = seconds 
     self.error_message = error_message 
    def handle_timeout(self, signum, frame): 
     raise TimeoutError(self.error_message) 
    def __enter__(self): 
     signal.signal(signal.SIGALRM, self.handle_timeout) 
     signal.alarm(self.seconds) 
    def __exit__(self, type, value, traceback): 
     signal.alarm(0) 
+6

Python Framester

+2

Sie könnten leicht in einen Dekorator '@ timeout.timeout' als statische Methode hinzufügen. Dann könnten Sie leicht zwischen einem Dekorateur oder einer 'mit' Aussage wählen. – Kevin

+5

Interessanterweise wird, wenn innerhalb des 'with Timeout (t)' -Kontexts irgendein Fehler auftritt, der '__exit__' immer noch aufgerufen, wodurch jegliche Komplikation vermieden wird, die durch das Auftreten von' TimeOutError' anstelle des tatsächlichen Fehlers verursacht wird. Dies ist eine sehr liebenswerte Lösung. – lucastamoios

Verwandte Themen