2010-02-17 17 views
116

Ho uno script di shell che scorre in un file di testo contenente URL: s che voglio visitare e fare screenshot di.Funzione di timeout se impiega troppo tempo a terminare

Tutto questo è fatto e semplice. Lo script inizializza una classe che, una volta eseguita, crea uno screenshot di ciascun sito nell'elenco. Alcuni siti richiedono molto tempo per essere caricati e alcuni potrebbero non essere caricati affatto. Quindi voglio avvolgere la funzione screengrabber in uno script di timeout, facendo in modo che la funzione restituisca False se non riesce a terminare entro 10 secondi.

Sono contento della soluzione più semplice possibile, forse impostando un timer asincrono che restituirà False dopo 10 secondi, indipendentemente da ciò che effettivamente accade all'interno della funzione?

+0

Per tutte le persone pigre, che amano utilizzare le librerie invece di copia + incolla frammenti di codice da StackOverflow: https://pypi.python.org/pypi/timeout-decorator – guettli

risposta

186

Il processo per il timeout di un'operazione è descritto nella documentazione di signal.

L'idea di base è utilizzare i gestori di segnale per impostare un allarme per un certo intervallo di tempo e generare un'eccezione una volta scaduto il timer.

Si noti che questo funzionerà solo su UNIX.

Ecco un'implementazione che crea un decoratore (salvare il codice seguente come 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 

Questo crea un decoratore chiamato @timeout che può essere applicato a tutte le funzioni di esecuzione prolungata.

Così, nel codice dell'applicazione, è possibile utilizzare il decoratore in questo modo:

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

Attenzione che questo non è thread-safe: se si utilizza il multithreading, il segnale verrà catturato da una discussione casuale. Tuttavia, per i programmi a thread singolo, questa è la soluzione più semplice. – Wim

+1

Bello. Inoltre, si raccomanda di decorare la funzione 'wrapper' con' @ functools.wraps (func) ' – shx2

+6

FYI, mancano i paren dopo il primo" @timeout ". Dovrebbe leggere '@timeout() def ...'. –

116

ho riscritto la risposta di David utilizzando l'istruzione with, permette di fare fare questo:

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

Quale solleverà un TimeoutError.

Il codice è ancora utilizzando signal e quindi solo UNIX:

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

Si potrebbe facilmente aggiungere un decoratore '@ timeout.timeout' come metodo statico a questo. Quindi, potresti facilmente scegliere tra un decoratore o un'istruzione 'with'. – Kevin

+5

È interessante notare che se all'interno del contesto 'with Timeout (t)' viene generato un errore, viene ancora chiamato '__exit__', evitando, quindi, qualsiasi complicazione causata da 'TimeOutError' che viene sollevata al posto dell'errore reale. Questa è una soluzione molto amabile. – lucastamoios

Problemi correlati