Python sleep (): comment ajouter des délais à votre code

Python sleep (): comment ajouter des délais à votre code

Avez-vous déjà eu besoin de faire attendre votre programme Python? La plupart du temps, vous souhaitez que votre code s’exécute le plus rapidement possible. Mais il y a des moments où laisser votre code dormir pendant un certain temps est en fait dans votre intérêt.

Par exemple, vous pouvez utiliser un appel Pythonsleep() pour simuler un retard dans votre programme. Vous devrez peut-être attendre qu'un fichier soit téléchargé ou téléchargé, ou qu'un graphique soit chargé ou affiché à l'écran. Vous devrez peut-être même faire une pause entre les appels à une API Web ou entre les requêtes vers une base de données. L'ajout d'appels Pythonsleep()à votre programme peut vous aider dans chacun de ces cas, et bien d'autres!

Dans ce didacticiel, vous apprendrez à ajouter des appels Pythonsleep()avec:

  • time.sleep()

  • Décorateurs

  • Les fils

  • Async IO

  • Interfaces utilisateur graphiques

Cet article est destiné aux développeurs intermédiaires qui cherchent à développer leurs connaissances de Python. Si cela vous ressemble, alors commençons!

Free Bonus:Get our free "The Power of Python Decorators" guide qui vous montre 3 modèles et techniques de décoration avancés que vous pouvez utiliser pour écrire dans des programmes plus propres et plus pythoniques.

Ajout d'un appel Pythonsleep() avectime.sleep()

Python a un support intégré pour mettre votre programme en veille. Le moduletime a une fonctionsleep() que vous pouvez utiliser pour suspendre l'exécution du thread appelant pendant le nombre de secondes que vous spécifiez.

Voici un exemple d'utilisation detime.sleep():

>>>

>>> import time
>>> time.sleep(3) # Sleep for 3 seconds

Si vous exécutez ce code dans votre console, vous devriez rencontrer un délai avant de pouvoir entrer une nouvelle instruction dans le REPL.

Note: Dans Python 3.5, les développeurs principaux ont légèrement modifié le comportement detime.sleep(). Le nouvel appel système de Pythonsleep()durera au moins le nombre de secondes que vous avez spécifié, même si le sommeil est interrompu par un signal. Cela ne s'applique cependant pas si le signal lui-même déclenche une exception.

Vous pouvez tester la durée du sommeil en utilisant le moduletimeit de Python:

$ python3 -m timeit -n 3 "import time; time.sleep(3)"
3 loops, best of 3: 3 sec per loop

Ici, vous exécutez le moduletimeit avec le paramètre-n, qui indique àtimeit combien de fois il faut exécuter l'instruction qui suit. Vous pouvez voir quetimeit a exécuté l'instruction 3 fois et que le meilleur temps d'exécution était de 3 secondes, ce qui était prévu.

Le nombre de fois quetimeit exécutera votre code par défaut est d'un million. Si vous exécutiez le code ci-dessus avec les-n par défaut, alors à 3 secondes par itération, votre terminal se bloquerait pendant environ 34 jours! Le moduletimeit a plusieurs autres options de ligne de commande que vous pouvez extraire dans sesdocumentation.

Créons quelque chose d'un peu plus réaliste. Un administrateur système doit savoir quand l'un de ses sites Web tombe en panne. Vous voulez pouvoir vérifier régulièrement le code d'état du site Web, mais vous ne pouvez pas interroger le serveur Web en permanence ou cela affectera les performances. Une façon de faire cette vérification consiste à utiliser un appel système Pythonsleep():

import time
import urllib.request
import urllib.error

def uptime_bot(url):
    while True:
        try:
            conn = urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
            # Email admin / log
            print(f'HTTPError: {e.code} for {url}')
        except urllib.error.URLError as e:
            # Email admin / log
            print(f'URLError: {e.code} for {url}')
        else:
            # Website is up
            print(f'{url} is up')
        time.sleep(60)

if __name__ == '__main__':
    url = 'http://www.google.com/py'
    uptime_bot(url)

Ici, vous créezuptime_bot(), qui prend une URL comme argument. La fonction tente ensuite d'ouvrir cette URL avecurllib. S'il y a unHTTPError ouURLError, le programme le détecte et affiche l'erreur. (Dans un environnement réel, vous enregistreriez l'erreur et enverriez probablement un e-mail au webmaster ou à l'administrateur système.)

Si aucune erreur ne se produit, votre code affiche que tout va bien. Peu importe ce qui se passe, votre programme se met en veille pendant 60 secondes. Cela signifie que vous n'accédez au site Web qu'une fois par minute. L'URL utilisée dans cet exemple est incorrecte, elle affichera donc les informations suivantes sur votre console toutes les minutes:

HTTPError: 404 for http://www.google.com/py

Allez-y et mettez à jour le code pour utiliser une bonne URL connue, commehttp://www.google.com. Ensuite, vous pouvez le réexécuter pour le voir fonctionner correctement. Vous pouvez également essayer de mettre à jour le code pour envoyer un e-mail ou consigner les erreurs. Pour plus d'informations sur la façon de procéder, consultezSending Emails With Python etLogging in Python.

Ajout d'un appel Pythonsleep() avec des décorateurs

Il y a des moments où vous devez réessayer une fonction qui a échoué. Un cas d'utilisation courant pour cela est lorsque vous devez réessayer un téléchargement de fichier car le serveur était occupé. Vous ne voudrez généralement pas faire une requête au serveur trop souvent, il est donc souhaitable d'ajouter un appel Pythonsleep()entre chaque requête.

Un autre cas d'utilisation que j'ai personnellement expérimenté est celui où je dois vérifier l'état d'une interface utilisateur lors d'un test automatisé. L'interface utilisateur peut se charger plus rapidement ou plus lentement que d'habitude, selon l'ordinateur sur lequel je lance le test. Cela peut changer ce qui est à l'écran au moment où mon programme vérifie quelque chose.

Dans ce cas, je peux dire au programme de s'endormir un instant, puis de revérifier les choses une seconde ou deux plus tard. Cela peut faire la différence entre un test réussi et un échec.

Vous pouvez utiliser undecorator pour ajouter un appel système Pythonsleep() dans l'un ou l'autre de ces cas. Si vous n'êtes pas familier avec les décorateurs ou si vous souhaitez les perfectionner, consultezPrimer on Python Decorators. Regardons un exemple:

import time
import urllib.request
import urllib.error

def sleep(timeout, retry=3):
    def the_real_decorator(function):
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < retry:
                try:
                    value = function(*args, **kwargs)
                    if value is None:
                        return
                except:
                    print(f'Sleeping for {timeout} seconds')
                    time.sleep(timeout)
                    retries += 1
        return wrapper
    return the_real_decorator

sleep() est votre décorateur. Il accepte une valeur detimeout et le nombre de fois où il devraitretry, qui vaut 3 par défaut. À l'intérieur desleep() se trouve une autre fonction,the_real_decorator(), qui accepte la fonction décorée.

Enfin, la fonction la plus internewrapper() accepte les arguments et les arguments de mot-clé que vous passez à la fonction décorée. C'est là que la magie opère! Vous utilisez une bouclewhile pour réessayer d'appeler la fonction. S'il y a une exception, vous appeleztime.sleep(), incrémentez le compteurretries et essayez d'exécuter à nouveau la fonction.

Réécrivez maintenantuptime_bot() pour utiliser votre nouveau décorateur:

@sleep(3)
def uptime_bot(url):
    try:
        conn = urllib.request.urlopen(url)
    except urllib.error.HTTPError as e:
        # Email admin / log
        print(f'HTTPError: {e.code} for {url}')
        # Re-raise the exception for the decorator
        raise urllib.error.HTTPError
    except urllib.error.URLError as e:
        # Email admin / log
        print(f'URLError: {e.code} for {url}')
        # Re-raise the exception for the decorator
        raise urllib.error.URLError
    else:
        # Website is up
        print(f'{url} is up')

if __name__ == '__main__':
    url = 'http://www.google.com/py'
    uptime_bot(url)

Ici, vous décorezuptime_bot() avec unsleep() de 3 secondes. Vous avez également supprimé la bouclewhile d'origine, ainsi que l'ancien appel àsleep(60). Le décorateur s'en charge maintenant.

Une autre modification que vous avez apportée consiste à ajouter unraise à l’intérieur des blocs de gestion des exceptions. C'est pour que le décorateur fonctionne correctement. Vous pouvez écrire au décorateur pour gérer ces erreurs, mais comme ces exceptions ne s'appliquent qu'àurllib, vous feriez peut-être mieux de garder le décorateur tel qu'il est. De cette façon, il fonctionnera avec une plus grande variété de fonctions.

Note: Si vous souhaitez vous familiariser avec la gestion des exceptions en Python, consultezPython Exceptions: An Introduction.

Il y a quelques améliorations que vous pourriez apporter à votre décorateur. S'il manque de nouvelles tentatives et échoue toujours, vous pouvez alors le faire re-déclencher la dernière erreur. Le décorateur attendra également 3 secondes après le dernier échec, ce qui pourrait ne pas arriver. N'hésitez pas à les essayer comme exercice!

Ajout d'un appel Pythonsleep() avec des threads

Il y a aussi des moments où vous voudrez peut-être ajouter un appel Pythonsleep() à unthread. Vous exécutez peut-être un script de migration sur une base de données contenant des millions d'enregistrements en production. Vous ne voulez pas provoquer de temps d'arrêt, mais vous ne voulez pas non plus attendre plus longtemps que nécessaire pour terminer la migration, vous décidez donc d'utiliser des threads.

Note: Les threads sont une méthode pour faire desconcurrency en Python. Vous pouvez exécuter plusieurs threads à la fois pour augmenter le débit de votre application. Si vous n'êtes pas familiarisé avec les threads en Python, consultezAn Intro to Threading in Python.

Pour empêcher les clients de remarquer tout type de ralentissement, chaque thread doit s'exécuter pendant une courte période, puis se mettre en veille. Il y a deux façons de faire ça:

  1. Utiliseztime.sleep() comme auparavant.

  2. UtilisezEvent.wait() du modulethreading.

Commençons par regardertime.sleep().

Utilisation detime.sleep()

Le PythonLogging Cookbook montre un bel exemple qui utilisetime.sleep(). Le modulelogging de Python est thread-safe, il est donc un peu plus utile que les instructionsprint() pour cet exercice. Le code suivant est basé sur cet exemple:

import logging
import threading
import time

def worker(arg):
    while not arg["stop"]:
        logging.debug("worker thread checking in")
        time.sleep(1)

def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(relativeCreated)6d %(threadName)s %(message)s"
    )
    info = {"stop": False}
    thread = threading.Thread(target=worker, args=(info,))
    thread_two = threading.Thread(target=worker, args=(info,))
    thread.start()
    thread_two.start()

    while True:
        try:
            logging.debug("Checking in from main thread")
            time.sleep(0.75)
        except KeyboardInterrupt:
            info["stop"] = True
            logging.debug('Stopping')
            break
    thread.join()
    thread_two.join()

if __name__ == "__main__":
    main()

Ici, vous utilisez le modulethreading de Python pour créer deux threads. Vous créez également un objet de journalisation qui enregistrera lesthreadName sur stdout. Ensuite, vous démarrez les deux threads et lancez une boucle pour vous connecter à partir du thread principal de temps en temps. Vous utilisezKeyboardInterrupt pour attraper l'utilisateur qui appuie surCtrl+[.kbd .key-c]#C #.

Essayez d'exécuter le code ci-dessus dans votre terminal. Vous devriez voir une sortie similaire à celle-ci:

 0 Thread-1 worker thread checking in
 1 Thread-2 worker thread checking in
 1 MainThread Checking in from main thread
752 MainThread Checking in from main thread
1001 Thread-1 worker thread checking in
1001 Thread-2 worker thread checking in
1502 MainThread Checking in from main thread
2003 Thread-1 worker thread checking in
2003 Thread-2 worker thread checking in
2253 MainThread Checking in from main thread
3005 Thread-1 worker thread checking in
3005 MainThread Checking in from main thread
3005 Thread-2 worker thread checking in

Lorsque chaque thread s'exécute puis se met en veille, la sortie de journalisation est imprimée sur la console. Maintenant que vous avez essayé un exemple, vous pourrez utiliser ces concepts dans votre propre code.

Utilisation deEvent.wait()

Le modulethreading fournit unEvent() que vous pouvez utiliser commetime.sleep(). Cependant,Event() a l'avantage supplémentaire d'être plus réactif. La raison en est que lorsque l'événement est défini, le programme sortira immédiatement de la boucle. Avectime.sleep(), votre code devra attendre que l'appel de Pythonsleep() se termine avant que le thread puisse se terminer.

La raison pour laquelle vous souhaitez utiliserwait() ici est quewait() estnon-blocking, alors quetime.sleep() estblocking. Cela signifie que lorsque vous utiliseztime.sleep(), vous empêcherez le thread principal de continuer à s’exécuter pendant qu’il attend la fin de l’appel desleep(). wait() résout ce problème. Vous pouvez en savoir plus sur le fonctionnement de tout cela dans lesthreading documentation de Python.

Voici comment ajouter un appel Pythonsleep() avecEvent.wait():

import logging
import threading

def worker(event):
    while not event.isSet():
        logging.debug("worker thread checking in")
        event.wait(1)

def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(relativeCreated)6d %(threadName)s %(message)s"
    )
    event = threading.Event()

    thread = threading.Thread(target=worker, args=(event,))
    thread_two = threading.Thread(target=worker, args=(event,))
    thread.start()
    thread_two.start()

    while not event.isSet():
        try:
            logging.debug("Checking in from main thread")
            event.wait(0.75)
        except KeyboardInterrupt:
            event.set()
            break

if __name__ == "__main__":
    main()

Dans cet exemple, vous créezthreading.Event() et le transmettez àworker(). (Rappelez-vous que dans l'exemple précédent, vous avez plutôt passé un dictionnaire.) Ensuite, vous configurez vos boucles pour vérifier sievent est défini ou non. Si ce n'est pas le cas, votre code imprime un message et attend un peu avant de vérifier à nouveau. Pour définir l'événement, vous pouvez appuyer sur [.keys] #Ctrl [.kbd .key-c] # C ##. Une fois l'événement défini, `+ worker ()` retournera et la boucle se rompra, mettant fin au programme.

Note: Si vous souhaitez en savoir plus sur les dictionnaires, consultezDictionaries in Python.

Examinez de plus près le bloc de code ci-dessus. Comment passeriez-vous un temps de sommeil différent à chaque thread de travail? Pouvez-vous le comprendre? N'hésitez pas à vous lancer seul dans cet exercice!

Ajout d'un appel Pythonsleep() avec Async IO

Des fonctionnalités asynchrones ont été ajoutées à Python dans la version 3.4, et cet ensemble de fonctionnalités s'est considérablement étendu depuis. Asynchronous programming est un type de programmation parallèle qui vous permet d'exécuter plusieurs tâches à la fois. Lorsqu'une tâche se termine, elle en informe le thread principal.

asyncio est un module qui vous permet d'ajouter un appel Pythonsleep() de manière asynchrone. Si vous n'êtes pas familier avec la mise en œuvre de la programmation asynchrone par Python, consultezAsync IO in Python: A Complete Walkthrough etPython Concurrency & Parallel Programming.

Voici un exemple des propresdocumentation de Python:

import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# Python 3.7+
asyncio.run(main())

Dans cet exemple, vous exécutezmain() et faites-le dormir pendant une seconde entre deux appelsprint().

Voici un exemple plus convaincant tiré de la partieCoroutines and Tasks de la documentation deasyncio:

import asyncio
import time

async def output(sleep, text):
    await asyncio.sleep(sleep)
    print(text)

async def main():
    print(f"Started: {time.strftime('%X')}")
    await output(1, 'First')
    await output(2, 'Second')
    await output(3, 'Third')
    print(f"Ended: {time.strftime('%X')}")

# Python 3.7+
asyncio.run(main())

Dans ce code, vous créez un worker appeléoutput() qui prend le nombre de secondes àsleep et lestext à imprimer. Ensuite, vous utilisez le mot-cléawait de Python pour attendre que le codeoutput() s'exécute. await est requis ici caroutput() a été marqué comme fonctionasync, vous ne pouvez donc pas l'appeler comme vous le feriez pour une fonction normale.

Lorsque vous exécutez ce code, votre programme exécuteraawait 3 fois. Le code attendra 1, 2 et 3 secondes, pour un temps d'attente total de 6 secondes. Vous pouvez également réécrire le code afin que les tâches s'exécutent en parallèle:

import asyncio
import time

async def output(text, sleep):
    while sleep > 0:
        await asyncio.sleep(1)
        print(f'{text} counter: {sleep} seconds')
        sleep -= 1

async def main():
    task_1 = asyncio.create_task(output('First', 1))
    task_2 = asyncio.create_task(output('Second', 2))
    task_3 = asyncio.create_task(output('Third', 3))
    print(f"Started: {time.strftime('%X')}")
    await task_1
    await task_2
    await task_3
    print(f"Ended: {time.strftime('%X')}")

if __name__ == '__main__':
    asyncio.run(main())

Vous utilisez maintenant le concept detasks, que vous pouvez créer aveccreate_task(). Lorsque vous utilisez des tâches dansasyncio, Python les exécutera de manière asynchrone. Ainsi, lorsque vous exécutez le code ci-dessus, il devrait se terminer en 3 secondes au total au lieu de 6.

Ajout d'un appel Pythonsleep() avec des interfaces graphiques

Les applications de ligne de commande ne sont pas le seul endroit où vous devrez peut-être ajouter des appels Pythonsleep(). Lorsque vous créez unGraphical User Interface (GUI), vous devrez parfois ajouter des délais. Par exemple, vous pouvez créer une application FTP pour télécharger des millions de fichiers, mais vous devez ajouter un appelsleep() entre les lots afin de ne pas enliser le serveur.

Le code GUI exécutera tous ses traitements et dessins dans un thread principal appeléevent loop. Si vous utiliseztime.sleep() à l'intérieur du code GUI, vous bloquerez sa boucle d'événements. Du point de vue de l'utilisateur, l'application peut sembler se bloquer. L'utilisateur ne pourra pas interagir avec votre application pendant son sommeil avec cette méthode. (Sous Windows, vous pouvez même recevoir une alerte indiquant que votre application ne répond plus.)

Heureusement, il existe d'autres méthodes que vous pouvez utiliser en plus detime.sleep(). Dans les prochaines sections, vous apprendrez à ajouter des appels Pythonsleep() à la fois dans Tkinter et wxPython.

Dormir à Tkinter

tkinter fait partie de la bibliothèque standard Python. Il peut ne pas être disponible si vous utilisez une version préinstallée de Python sur Linux ou Mac. Si vous obtenez unImportError, vous devrez vous demander comment l’ajouter à votre système. Mais si vousinstall Python yourself, alorstkinter devrait déjà être disponible.

Vous allez commencer par regarder un exemple qui utilisetime.sleep(). Exécutez ce code pour voir ce qui se passe lorsque vous ajoutez un appel Pythonsleep() dans le mauvais sens:

import tkinter
import time

class MyApp:
    def __init__(self, parent):
        self.root = parent
        self.root.geometry("400x400")
        self.frame = tkinter.Frame(parent)
        self.frame.pack()
        b = tkinter.Button(text="click me", command=self.delayed)
        b.pack()

    def delayed(self):
        time.sleep(3)

if __name__ == "__main__":
    root = tkinter.Tk()
    app = MyApp(root)
    root.mainloop()

Une fois que vous avez exécuté le code, appuyez sur le bouton dans votre interface graphique. Le bouton restera enfoncé pendant trois secondes en attendant quesleep() se termine. Si l'application avait d'autres boutons, vous ne pourriez pas cliquer dessus. Vous ne pouvez pas non plus fermer l'application pendant son sommeil, car elle ne peut pas répondre à l'événement de fermeture.

Pour quetkinter s'endorme correctement, vous devez utiliserafter():

import tkinter

class MyApp:
    def __init__(self, parent):
        self.root = parent
        self.root.geometry("400x400")
        self.frame = tkinter.Frame(parent)
        self.frame.pack()
        self.root.after(3000, self.delayed)

    def delayed(self):
        print('I was delayed')

if __name__ == "__main__":
    root = tkinter.Tk()
    app = MyApp(root)
    root.mainloop()

Ici, vous créez une application de 400 pixels de large par 400 pixels de haut. Il n'a pas de widgets dessus. Tout ce qu'il fera, c'est montrer un cadre. Ensuite, vous appelezself.root.after()self.root est une référence à l'objetTk(). after() prend deux arguments:

  1. Le nombre de millisecondes pour dormir

  2. La méthode à appeler lorsque le sommeil est terminé

Dans ce cas, votre application imprimera une chaîne sur stdout après 3 secondes. Vous pouvez considérerafter() comme la versiontkinter detime.sleep(), mais cela ajoute également la possibilité d'appeler une fonction une fois le sommeil terminé.

Vous pouvez utiliser cette fonctionnalité pour améliorer l'expérience utilisateur. En ajoutant un appel Pythonsleep(), vous pouvez donner l'impression que l'application se charge plus rapidement, puis démarrer un processus plus long après son exécution. De cette façon, l'utilisateur n'aura pas à attendre l'ouverture de l'application.

Dormir dans wxPython

Il existe deux différences majeures entre wxPython et Tkinter:

  1. wxPython a beaucoup plus de widgets.

  2. wxPython vise à avoir une apparence native sur toutes les plateformes.

Le framework wxPython n'est pas inclus avec Python, vous devrez donc l'installer vous-même. Si vous ne connaissez pas wxPython, consultezHow to Build a Python GUI Application With wxPython.

Dans wxPython, vous pouvez utiliserwx.CallLater() pour ajouter un appel Pythonsleep():

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        wx.CallLater(4000, self.delayed)
        self.Show()

    def delayed(self):
        print('I was delayed')

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Ici, vous sous-classez directementwx.Frame, puis appelezwx.CallLater(). Cette fonction prend les mêmes paramètres que lesafter() de Tkinter:

  1. Le nombre de millisecondes pour dormir

  2. La méthode à appeler lorsque le sommeil est terminé

Lorsque vous exécutez ce code, vous devriez voir une petite fenêtre vide apparaître sans aucun widget. Après 4 secondes, vous verrez la chaîne'I was delayed' imprimée sur stdout.

L’un des avantages de l’utilisation dewx.CallLater() est qu’il est thread-safe. Vous pouvez utiliser cette méthode à partir d'un thread pour appeler une fonction qui se trouve dans l'application wxPython principale.

Conclusion

Avec ce tutoriel, vous avez acquis une nouvelle technique précieuse à ajouter à votre boîte à outils Python! Vous savez ajouter des délais pour accélérer vos applications et les empêcher d'utiliser les ressources système. Vous pouvez même utiliser les appels Pythonsleep() pour aider votre code GUI à se redessiner plus efficacement. Cela rendra l'expérience utilisateur bien meilleure pour vos clients!

Pour récapituler, vous avez appris à ajouter des appels Pythonsleep() avec les outils suivants:

  • time.sleep()

  • Décorateurs

  • Les fils

  • asyncio

  • Tkinter

  • wxPython

Vous pouvez maintenant prendre ce que vous avez appris et commencer à mettre votre code en veille!