Réponses:
Vous pouvez utiliser le package de signaux si vous exécutez sous UNIX:
In [1]: import signal
# Register an handler for the timeout
In [2]: def handler(signum, frame):
...: print("Forever is over!")
...: raise Exception("end of time")
...:
# This function *may* run for an indetermined time...
In [3]: def loop_forever():
...: import time
...: while 1:
...: print("sec")
...: time.sleep(1)
...:
...:
# Register the signal function handler
In [4]: signal.signal(signal.SIGALRM, handler)
Out[4]: 0
# Define a timeout for your function
In [5]: signal.alarm(10)
Out[5]: 0
In [6]: try:
...: loop_forever()
...: except Exception, exc:
...: print(exc)
....:
sec
sec
sec
sec
sec
sec
sec
sec
Forever is over!
end of time
# Cancel the timer if the function returned before timeout
# (ok, mine won't but yours maybe will :)
In [7]: signal.alarm(0)
Out[7]: 0
10 secondes après l'appel alarm.alarm(10)
, le gestionnaire est appelé. Cela déclenche une exception que vous pouvez intercepter à partir du code Python standard.
Ce module ne fonctionne pas bien avec les threads (mais alors, qui le fait?)
Notez que puisque nous levons une exception lorsque le délai d'expiration se produit, elle peut finir par être interceptée et ignorée à l'intérieur de la fonction, par exemple d'une de ces fonctions:
def loop_forever():
while 1:
print('sec')
try:
time.sleep(10)
except:
continue
signal.alarm
et les éléments associés SIGALRM
ne sont pas disponibles sur les plates-formes Windows.
signal.signal
--- fonctionnera-t-il tous correctement? Est-ce que chaque signal.signal
appel n'en annulera pas un "simultané"?
Vous pouvez utiliser multiprocessing.Process
pour faire exactement cela.
Code
import multiprocessing
import time
# bar
def bar():
for i in range(100):
print "Tick"
time.sleep(1)
if __name__ == '__main__':
# Start bar as a process
p = multiprocessing.Process(target=bar)
p.start()
# Wait for 10 seconds or until process finishes
p.join(10)
# If thread is still active
if p.is_alive():
print "running... let's kill it..."
# Terminate
p.terminate()
p.join()
join()
. qui rend votre x nombre de sous - processus simultanés étant en cours d' exécution jusqu'à ce les terminer leur travail, ou un montant défini dans join(10)
. Si vous avez une E / S bloquante pour 10 processus, en utilisant join (10), vous les avez configurés pour les attendre tous au maximum 10 pour CHAQUE processus qui a démarré. Utilisez l'indicateur démon comme cet exemple stackoverflow.com/a/27420072/2480481 . Bien sûr, vous pouvez passer le drapeau daemon=True
directement pour multiprocessing.Process()
fonctionner.
terminate() ... Note that exit handlers and finally clauses, etc., will not be executed. Note that descendant processes of the process will not be terminated – they will simply become orphaned.
Comment appeler la fonction ou comment l'envelopper pour que si elle prend plus de 5 secondes, le script l'annule?
J'ai posté un résumé qui résout cette question / problème avec un décorateur et un threading.Timer
. Le voici avec une panne.
Il a été testé avec Python 2 et 3. Il devrait également fonctionner sous Unix / Linux et Windows.
D'abord les importations. Ceux-ci tentent de garder le code cohérent quelle que soit la version de Python:
from __future__ import print_function
import sys
import threading
from time import sleep
try:
import thread
except ImportError:
import _thread as thread
Utilisez un code indépendant de la version:
try:
range, _print = xrange, print
def print(*args, **kwargs):
flush = kwargs.pop('flush', False)
_print(*args, **kwargs)
if flush:
kwargs.get('file', sys.stdout).flush()
except NameError:
pass
Maintenant, nous avons importé nos fonctionnalités de la bibliothèque standard.
exit_after
décorateurEnsuite, nous avons besoin d'une fonction pour terminer le à main()
partir du thread enfant:
def quit_function(fn_name):
# print to stderr, unbuffered in Python 2.
print('{0} took too long'.format(fn_name), file=sys.stderr)
sys.stderr.flush() # Python 3 stderr is likely buffered.
thread.interrupt_main() # raises KeyboardInterrupt
Et voici le décorateur lui-même:
def exit_after(s):
'''
use as decorator to exit process if
function takes longer than s seconds
'''
def outer(fn):
def inner(*args, **kwargs):
timer = threading.Timer(s, quit_function, args=[fn.__name__])
timer.start()
try:
result = fn(*args, **kwargs)
finally:
timer.cancel()
return result
return inner
return outer
Et voici l'utilisation qui répond directement à votre question sur la sortie après 5 secondes!:
@exit_after(5)
def countdown(n):
print('countdown started', flush=True)
for i in range(n, -1, -1):
print(i, end=', ', flush=True)
sleep(1)
print('countdown finished')
Démo:
>>> countdown(3)
countdown started
3, 2, 1, 0, countdown finished
>>> countdown(10)
countdown started
10, 9, 8, 7, 6, countdown took too long
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in inner
File "<stdin>", line 6, in countdown
KeyboardInterrupt
Le deuxième appel de fonction ne se terminera pas, à la place, le processus devrait se terminer avec une trace!
KeyboardInterrupt
n'arrête pas toujours un fil endormiNotez que le sommeil ne sera pas toujours interrompu par une interruption clavier, sur Python 2 sous Windows, par exemple:
@exit_after(1)
def sleep10():
sleep(10)
print('slept 10 seconds')
>>> sleep10()
sleep10 took too long # Note that it hangs here about 9 more seconds
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in inner
File "<stdin>", line 3, in sleep10
KeyboardInterrupt
il n'est pas non plus susceptible d'interrompre le code en cours d'exécution dans les extensions à moins qu'il ne le vérifie explicitement PyErr_CheckSignals()
, voir Cython, Python et KeyboardInterrupt ignorés
J'éviterais de dormir un thread plus d'une seconde, en tout cas - c'est une éon en temps de processeur.
Comment appeler la fonction ou comment l'envelopper pour que si cela prend plus de 5 secondes, le script l'annule et fasse autre chose?
Pour l'attraper et faire autre chose, vous pouvez attraper le KeyboardInterrupt.
>>> try:
... countdown(10)
... except KeyboardInterrupt:
... print('do something else')
...
countdown started
10, 9, 8, 7, 6, countdown took too long
do something else
thread.interrupt_main()
, pourquoi ne puis-je pas lever directement une exception?
multiprocessing.connection.Client
? - Essayer de résoudre: stackoverflow.com/questions/57817955/…
J'ai une proposition différente qui est une fonction pure (avec la même API que la suggestion de filetage) et semble fonctionner correctement (basée sur les suggestions de ce fil)
def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None):
import signal
class TimeoutError(Exception):
pass
def handler(signum, frame):
raise TimeoutError()
# set the timeout handler
signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout_duration)
try:
result = func(*args, **kwargs)
except TimeoutError as exc:
result = default
finally:
signal.alarm(0)
return result
timeout
. Il est préférable de définir la valeur par défaut sur None
et, sur la première ligne de la fonction, d'ajouter kwargs = kwargs or {}
. Args est correct car les tuples ne sont pas modifiables.
J'ai parcouru ce fil lors de la recherche d'un appel de délai d'attente sur les tests unitaires. Je n'ai rien trouvé de simple dans les réponses ou les packages tiers, j'ai donc écrit le décorateur ci-dessous, vous pouvez le déposer directement dans le code:
import multiprocessing.pool
import functools
def timeout(max_timeout):
"""Timeout decorator, parameter in seconds."""
def timeout_decorator(item):
"""Wrap the original function."""
@functools.wraps(item)
def func_wrapper(*args, **kwargs):
"""Closure for function."""
pool = multiprocessing.pool.ThreadPool(processes=1)
async_result = pool.apply_async(item, args, kwargs)
# raises a TimeoutError if execution exceeds max_timeout
return async_result.get(max_timeout)
return func_wrapper
return timeout_decorator
Ensuite, c'est aussi simple que cela pour expirer un test ou une fonction que vous aimez:
@timeout(5.0) # if execution takes longer than 5 seconds, raise a TimeoutError
def test_base_regression(self):
...
Exception
intérieur de func_wrapper et faire pool.close()
après la capture pour vous assurer que le thread meurt toujours après quoi qu'il arrive . Ensuite, vous pouvez lancer TimeoutError
ou ce que vous voulez après. Semble fonctionner pour moi.
RuntimeError: can't start new thread
. Cela fonctionnera-t-il toujours si je l'ignore ou est-ce que je peux faire autre chose pour contourner cela? Merci d'avance!
Le stopit
package, trouvé sur pypi, semble bien gérer les délais d'attente.
J'aime le @stopit.threading_timeoutable
décorateur, qui ajoute un timeout
paramètre à la fonction décorée, qui fait ce que vous attendez, il arrête la fonction.
Découvrez-le sur pypi: https://pypi.python.org/pypi/stopit
Il y a beaucoup de suggestions, mais aucune n'utilise simult.futures, ce qui, à mon avis, est la façon la plus lisible de gérer cela.
from concurrent.futures import ProcessPoolExecutor
# Warning: this does not terminate function if timeout
def timeout_five(fnc, *args, **kwargs):
with ProcessPoolExecutor() as p:
f = p.submit(fnc, *args, **kwargs)
return f.result(timeout=5)
Super simple à lire et à entretenir.
Nous créons un pool, soumettons un seul processus, puis attendons jusqu'à 5 secondes avant de déclencher une TimeoutError que vous pouvez intercepter et gérer comme bon vous semble.
Natif de python 3.2+ et rétroporté à 2.7 (pip install futures).
Basculer entre les threads et les processus est aussi simple que de le remplacer ProcessPoolExecutor
parThreadPoolExecutor
.
Si vous souhaitez mettre fin au processus à l'expiration du délai, je vous suggère d'examiner Pebble .
Générateur de délai d'expiration du projet PyPi , facile à utiliser et fiable ( https://pypi.org/project/timeout-decorator/ )
installation :
pip install timeout-decorator
Utilisation :
import time
import timeout_decorator
@timeout_decorator.timeout(5)
def mytest():
print "Start"
for i in range(1,10):
time.sleep(1)
print "%d seconds have passed" % i
if __name__ == '__main__':
mytest()
Je suis l'auteur de wrapt_timeout_decorator
La plupart des solutions présentées ici fonctionnent à merveille sous Linux à première vue - parce que nous avons fork () et signaux () - mais sur Windows, les choses semblent un peu différentes. Et quand il s'agit de sous-threads sur Linux, vous ne pouvez plus utiliser de signaux.
Pour générer un processus sous Windows, il doit être sélectionnable - et de nombreuses fonctions décorées ou méthodes de classe ne le sont pas.
Vous devez donc utiliser un meilleur pickler comme l'aneth et le multiprocess (pas le pickle et le multiprocessing) - c'est pourquoi vous ne pouvez pas utiliser ProcessPoolExecutor (ou seulement avec des fonctionnalités limitées).
Pour le délai lui-même - Vous devez définir ce que signifie le délai d'attente - car sous Windows, il faudra un temps considérable (et non déterminable) pour lancer le processus. Cela peut être délicat sur de courts délais. Supposons que la génération du processus prenne environ 0,5 seconde (facilement !!!). Si vous donnez un délai de 0,2 seconde, que devrait-il se passer? La fonction doit-elle expirer après 0,5 + 0,2 seconde (alors laissez la méthode s'exécuter pendant 0,2 seconde)? Ou le processus appelé devrait-il expirer après 0,2 seconde (dans ce cas, la fonction décorée expire TOUJOURS, car pendant ce temps, elle n'est même pas générée)?
Les décorateurs imbriqués peuvent également être méchants et vous ne pouvez pas utiliser de signaux dans un sous-fil. Si vous voulez créer un décorateur multiplateforme véritablement universel, tout cela doit être pris en considération (et testé).
D'autres problèmes transmettent des exceptions à l'appelant, ainsi que des problèmes de journalisation (s'ils sont utilisés dans la fonction décorée - la journalisation dans des fichiers dans un autre processus n'est PAS prise en charge)
J'ai essayé de couvrir tous les cas marginaux, vous pouvez examiner le package wrapt_timeout_decorator, ou au moins tester vos propres solutions en vous inspirant des tests non utilisés.
@Alexis Eggermont - malheureusement, je n'ai pas assez de points à commenter - peut-être que quelqu'un d'autre peut vous en informer - je pense avoir résolu votre problème d'importation.
timeout-decorator
ne fonctionne pas sur le système Windows car Windows ne prend pas en charge signal
bien.
Si vous utilisez timeout-decorator dans le système Windows, vous obtiendrez les éléments suivants
AttributeError: module 'signal' has no attribute 'SIGALRM'
Certains ont suggéré d'utiliser use_signals=False
mais n'ont pas fonctionné pour moi.
L'auteur @bitranox a créé le package suivant:
pip install https://github.com/bitranox/wrapt-timeout-decorator/archive/master.zip
Échantillon de code:
import time
from wrapt_timeout_decorator import *
@timeout(5)
def mytest(message):
print(message)
for i in range(1,10):
time.sleep(1)
print('{} seconds have passed'.format(i))
def main():
mytest('starting')
if __name__ == '__main__':
main()
Donne l'exception suivante:
TimeoutError: Function mytest timed out after 5 seconds
from wrapt_timeout_decorator import *
semble tuer certaines de mes autres importations. Par exemple ModuleNotFoundError: No module named 'google.appengine'
, j'obtiens , mais je n'obtiens pas cette erreur si je n'importe pas wrapt_timeout_decorator
Nous pouvons utiliser des signaux pour la même chose. Je pense que l'exemple ci-dessous vous sera utile. C'est très simple par rapport aux threads.
import signal
def timeout(signum, frame):
raise myException
#this is an infinite loop, never ending under normal circumstances
def main():
print 'Starting Main ',
while 1:
print 'in main ',
#SIGALRM is only usable on a unix platform
signal.signal(signal.SIGALRM, timeout)
#change 5 to however many seconds you need
signal.alarm(5)
try:
main()
except myException:
print "whoops"
try: ... except: ...
sont toujours une mauvaise idée.
#!/usr/bin/python2
import sys, subprocess, threading
proc = subprocess.Popen(sys.argv[2:])
timer = threading.Timer(float(sys.argv[1]), proc.terminate)
timer.start()
proc.wait()
timer.cancel()
exit(proc.returncode)
J'avais besoin de interruptions chronométrées emboîtables (que SIGALARM ne peut pas faire) qui ne seront pas bloquées par time.sleep (ce que l'approche basée sur les threads ne peut pas faire). J'ai fini par copier et modifier légèrement le code d'ici: http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/
Le code lui-même:
#!/usr/bin/python
# lightly modified version of http://code.activestate.com/recipes/577600-queue-for-managing-multiple-sigalrm-alarms-concurr/
"""alarm.py: Permits multiple SIGALRM events to be queued.
Uses a `heapq` to store the objects to be called when an alarm signal is
raised, so that the next alarm is always at the top of the heap.
"""
import heapq
import signal
from time import time
__version__ = '$Revision: 2539 $'.split()[1]
alarmlist = []
__new_alarm = lambda t, f, a, k: (t + time(), f, a, k)
__next_alarm = lambda: int(round(alarmlist[0][0] - time())) if alarmlist else None
__set_alarm = lambda: signal.alarm(max(__next_alarm(), 1))
class TimeoutError(Exception):
def __init__(self, message, id_=None):
self.message = message
self.id_ = id_
class Timeout:
''' id_ allows for nested timeouts. '''
def __init__(self, id_=None, seconds=1, error_message='Timeout'):
self.seconds = seconds
self.error_message = error_message
self.id_ = id_
def handle_timeout(self):
raise TimeoutError(self.error_message, self.id_)
def __enter__(self):
self.this_alarm = alarm(self.seconds, self.handle_timeout)
def __exit__(self, type, value, traceback):
try:
cancel(self.this_alarm)
except ValueError:
pass
def __clear_alarm():
"""Clear an existing alarm.
If the alarm signal was set to a callable other than our own, queue the
previous alarm settings.
"""
oldsec = signal.alarm(0)
oldfunc = signal.signal(signal.SIGALRM, __alarm_handler)
if oldsec > 0 and oldfunc != __alarm_handler:
heapq.heappush(alarmlist, (__new_alarm(oldsec, oldfunc, [], {})))
def __alarm_handler(*zargs):
"""Handle an alarm by calling any due heap entries and resetting the alarm.
Note that multiple heap entries might get called, especially if calling an
entry takes a lot of time.
"""
try:
nextt = __next_alarm()
while nextt is not None and nextt <= 0:
(tm, func, args, keys) = heapq.heappop(alarmlist)
func(*args, **keys)
nextt = __next_alarm()
finally:
if alarmlist: __set_alarm()
def alarm(sec, func, *args, **keys):
"""Set an alarm.
When the alarm is raised in `sec` seconds, the handler will call `func`,
passing `args` and `keys`. Return the heap entry (which is just a big
tuple), so that it can be cancelled by calling `cancel()`.
"""
__clear_alarm()
try:
newalarm = __new_alarm(sec, func, args, keys)
heapq.heappush(alarmlist, newalarm)
return newalarm
finally:
__set_alarm()
def cancel(alarm):
"""Cancel an alarm by passing the heap entry returned by `alarm()`.
It is an error to try to cancel an alarm which has already occurred.
"""
__clear_alarm()
try:
alarmlist.remove(alarm)
heapq.heapify(alarmlist)
finally:
if alarmlist: __set_alarm()
et un exemple d'utilisation:
import alarm
from time import sleep
try:
with alarm.Timeout(id_='a', seconds=5):
try:
with alarm.Timeout(id_='b', seconds=2):
sleep(3)
except alarm.TimeoutError as e:
print 'raised', e.id_
sleep(30)
except alarm.TimeoutError as e:
print 'raised', e.id_
else:
print 'nope.'
Voici une légère amélioration de la solution basée sur les threads donnée.
Le code ci-dessous prend en charge les exceptions :
def runFunctionCatchExceptions(func, *args, **kwargs):
try:
result = func(*args, **kwargs)
except Exception, message:
return ["exception", message]
return ["RESULT", result]
def runFunctionWithTimeout(func, args=(), kwargs={}, timeout_duration=10, default=None):
import threading
class InterruptableThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.result = default
def run(self):
self.result = runFunctionCatchExceptions(func, *args, **kwargs)
it = InterruptableThread()
it.start()
it.join(timeout_duration)
if it.isAlive():
return default
if it.result[0] == "exception":
raise it.result[1]
return it.result[1]
L'invoquer avec un délai de 5 secondes:
result = timeout(remote_calculate, (myarg,), timeout_duration=5)
runFunctionCatchExceptions()
certaines fonctions Python l'obtention de GIL était appelée. Par exemple , le suivant ne serait jamais, ou très longtemps, le retour si elle est appelée dans la fonction: eval(2**9999999999**9999999999)
. Voir stackoverflow.com/questions/22138190/…