Mettre un Timeout sur une fonction en python

Je vais vous présenter deux manières de limiter le temps d’éxécution d’une fonction/méthode en python.

La première est très élégante et inspirée de ce site, mais les bugs sont corrigés. Malheureusement dans un cas particulier (l’utilisation d’un wrapper vers des fonctions en C (swiglpk)) cela ne fonctionnait pas et j’ai dû trouver une autre solution.

La solution élégante

En premier lieu vous devez importer le module signal puis créer une nouvelle exception:

import signal
class TimeoutException(Exception):
    pass

Ensuite, il faut créer le décorateur. C’est un peu plus compliqué à comprendre. Le décorateur va créer une alarme puis il va lancer la fonction à laquelle vous l’avez accolé. Si la durée d’éxécution est plus grande que la limite que vous avez définie alors un signal va être lancé par l’alarme. Le handler va récupérer le signal et va lancer une exception qui termine l’éxécution courante jusqu’à la fonction où vous la récupérez. Sinon l’alarme va être réinitialisée et le résultat du calcul de votre fonction va être renvoyé.
Cela ce code ainsi:

def deadline(timeout, *args):
    """is a the decotator name with the timeout parameter in second"""
    def decorate(f):
        """ the decorator creation """
        def handler(signum, frame):
            """ the handler for the timeout """
            raise TimeoutException() #when the signal have been handle raise the exception

        def new_f(*args):
            """ the initiation of the handler, 
            the lauch of the function and the end of it"""
            signal.signal(signal.SIGALRM, handler) #link the SIGALRM signal to the handler
            signal.alarm(timeout) #create an alarm of timeout second
            res = f(*args) #lauch the decorate function with this parameter
            signal.alarm(0) #reinitiate the alarm
            return res #return the return value of the fonction
    
        new_f.__name__ = f.__name__
        return new_f

Ensuite, ça s’utilise simplement comme ceci :

@deadline(1) #time limit of execution of 1 second
def longfunction(x, y):
    res = 0    
    while True:
        res += x*y

def call_of_longfunction():
    try:
        longfunction(1, 3)
    except TimeoutException:
         print "too long"

La solution robuste

En premier lieu vous devez importer Queue et Process du module multiprocessing :

from multiprocessing import Process, Queue

Puis vous pouvez créer la fonction prenant du temps. Au lieu de faire “return resultat” vous devez utiliser queue.put(resultat) et queue doit être un paramètre de votre fonction.

def longfunction(x, y, queue):
    res = 0
    while True:
        res += x*y
    queue.put(res)

Vous pouvez enfin créer la fonction qui va limiter la durée d’éxécution :

def call_of_longfunction():
    queue = Queue() #using to get the result
    proc = Process(target=longfunction, args=(1, 3, queue,)) #creation of a process calling longfunction with the specified arguments
    proc.start() #lauching the processus on another thread
    try:
        res = queue.get(timeout=1) #getting the resultat under 1 second or stop
        proc.join() #proper delete if the computation has take less than timeout seconds
    except: #catching every exception type
        proc.terminate() #kill the process
        print "too long"

Il est peut être possible de créer un décorateur pour cette solution robuste, mais cela sera difficile à cause de l’utilisation de Queue et du tuple d’arguments de Process devant finir par “,”.

Mettre un Timeout sur une fonction en python par La Réponse est 42 est sous Licence Creative Commons Internationale Attribution-Partage à l'identique 4.0.

Vous aimerez aussi...

1 réponse

  1. François dit :

    Merci pour le partage !
    Dans la solution élégante, il manque juste une ligne. Il faut ajouter :

    return decorate

    à la fin de la fonction deadline() pour que çà marche.
    (Sinon, on obtient le message TypeError: ‘NoneType’ object is not callable)

    Amicalement,
    François

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *